Compare commits

...

237 Commits

Author SHA1 Message Date
Peter Johnson
b9bd3e5754 Merge branch 'main' into 2027 2025-10-23 22:33:14 -07:00
PJ Reiniger
44b9cc1398 [robotpy] Mirror most other subprojects (#8208)
GitOrigin-RevId: ac60fd3cf4a24023184376687da28373d14b781a

This mirrors the robotpy files for the following projects:
- apriltag
- datalog
- hal
- ntcore
- romiVendordep
- wpilibc
- wpimath
- xrpVendordep

This excludes cscore and the halsim wrappers for at this time.

NOTE: This does not hook these projects up to the build system, just simply mirrors the files. The building will take place in a follow up PR to make it easier to review the changes necessary to build.
2025-10-23 22:28:04 -07:00
Jade
8992dcdc99 [wpilib] Remove Driverstation.waitForDsConnection (#8288)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-10-22 18:43:29 -07:00
Peter Johnson
7a2a982e66 Merge branch 'main' into 2027 2025-10-11 23:54:41 -07:00
Sam Carlberg
b37e2d9343 [commands] Add Commands v3 framework (#6518)
The framework fundamentally relies on the continuation API added in Java 21 (which is currently internal to the JDK). Continuations allow for call stacks to be saved to the heap and resumed later.

The async framework allows command bodies to be written in an imperative style. However, an async command will need to be actively cooperative and periodically call coroutine.yield() in loops to yield control back to the command scheduler to let it process other commands.

There are also some other additions like priority levels (as opposed to a blanket yes/no for ignoring incoming commands), factories requiring names be provided for commands, and the scheduler tracking all running commands and not just the highest-level groups. However, those changes aren't unique to an async framework, and could just as easily be used in a traditional command framework.
2025-10-10 13:47:22 -07:00
Ryan Blue
33f91589b4 [wpilib, examples] Remove AnalogGyro (#8205) 2025-10-10 12:44:39 -07:00
Sam Carlberg
35e4a18e86 [cmd, build] Fix wpiannotation dependencies on commandsv2 (#8279) 2025-10-07 21:50:38 -07:00
Peter Johnson
7ff312bb69 Merge branch 'main' into 2027 2025-10-06 19:43:02 -07:00
Gold856
b1aaabc1c6 [hal] Remove FPGA functions that won't exist on SC (#8273) 2025-10-04 15:58:12 -07:00
Austin Schuh
5c719ced5f [bazel] Put eigen in an external repo like bzlmod (#8169)
This sets us up to use AOS, which wants @eigen to resolve, without
introducing a second version or copy of eigen.
2025-10-03 12:32:40 -07:00
Thad House
7d34f43e44 [wpilib] Replace internal usages of Analog getAverageVoltage (#8271) 2025-10-02 22:40:45 -07:00
Thad House
e369c721ca [wpilib] Remove Servo Classes (#8270)
SystemCore doesn't directly support Servos. It would be possible to still use a Servo Power Module, but those are fairly rare, and we should probably use a different class for that case, so users don't attempt to hook a servo directly up to systemcore. That will depend on what happens with the rules in 2027.

Rev Servo Hubs are a current working replacement for systemcore users.
2025-10-02 22:39:55 -07:00
PJ Reiniger
c46b54a523 [wpilibj] Use non-global NetworkTableInstance in alert test (#8265) 2025-09-29 17:45:37 -07:00
PJ Reiniger
7290766482 [epilogue-processor] Fix naive tests in prep for huge refactor (#8255) 2025-09-29 11:47:51 -07:00
Tyler Veness
bd5141e254 [upstream_utils] Upgrade to fmt 12.0.0 (#8263) 2025-09-29 11:45:15 -07:00
Jonah Bonner
e364087e99 [glass] Fix color order for sim GUI LEDs (#8264) 2025-09-29 11:44:27 -07:00
Gold856
7aabc19977 [bazel] Update Bazel to 8.4.1 to fix Windows builds (#8258)
- Fix missing epilogue deps
- Compress debug info to save space in CI
2025-09-27 22:50:21 -07:00
Peter Johnson
ef676aca70 Merge branch 'main' into 2027 2025-09-25 22:17:00 -07:00
Sam Carlberg
cf4f6ce4b6 Update maven_install.json to match maven artifacts list in WORKSPACE (#8248)
Use `REPIN=1 bazel run @maven//:pin` to regenerate. `REPIN=1` is necessary for the conflicting gson versions to be resolved.
2025-09-21 20:44:43 -04:00
Caitlin Cai
76ee08e0b4 [readme] Add instructions for installing SystemCore toolchain (#8182) 2025-09-20 11:24:05 -07:00
Tyler Veness
f701132392 [wpimath] Refactor MathUtil.interpolate() and MathUtil.inverseInterpolate() to handle extrapolation (#8232) 2025-09-20 11:22:05 -07:00
Peter Johnson
1ce2854a1e Merge branch 'main' into 2027 2025-09-20 11:19:40 -07:00
Gold856
d59f1732bb [glass] Fix handling for optionals and empty arrays (#8227) 2025-09-14 07:46:51 -07:00
Peter Johnson
d5d67d874b [glass] NetworkTables: Show struct enum values (#8224)
Also change the format for both proto and struct enums to show other values as <N>, and not put quotes around the value.
2025-09-12 13:01:50 -07:00
Peter Johnson
8c20da44c8 [hal] Remove old netcomm types (#8225) 2025-09-12 07:29:05 -07:00
Peter Johnson
855ef10d58 [wpiutil] DynamicStruct: Make GetEnumValues() const (#8223)
Also remove unnecessary include.
2025-09-12 07:27:43 -07:00
Peter Johnson
90d90f334d [wpiutil] Add alloc_wpi_string() and copy_wpi_string() (#8222)
These are useful to allocate a WPI_String, rather than referencing existing data.
2025-09-12 07:27:09 -07:00
Sam Carlberg
909f8a1dc4 [build] Remove PMD.RedundantFieldInitializer rule (#8184)
Code readability is much more important than saving three bytes per redundantly-initialized field.
2025-09-08 21:19:00 -07:00
Tyler Veness
be72d543ad [wpilib] Remove robotInit() (#8199)
Fixes #6622.
2025-09-08 21:17:37 -07:00
Peter Johnson
f8ed2a4d92 [wpiutil] Synchronization: fix shutdown use-after-free (#8213)
Also in ntcore, join the notifier thread on shutdown.
This prevents tsan from reporting it as a leaked thread.
2025-09-08 21:15:00 -07:00
Gold856
5cd97c6353 [upstream_utils] Add Catch2 (#8203) 2025-09-08 15:14:23 -06:00
Peter Johnson
e8490eb242 [ntcore] Disable assertions unit test (ValueDeathTest) (#8221)
This likes to hang on Windows.
2025-09-08 14:58:09 -06:00
Peter Johnson
e5b7613b78 [ntcore] Change ntcore_test to just exit on tsan error (#8220)
In shutdown situations, calling FAIL() can deadlock in tsan.
2025-09-08 14:57:32 -06:00
PJ Reiniger
bd1dcc4358 [bazel][robotpy] Add mirror for robotpy's wpiuil and wpinet libraries (#8062)
Project import generated by Copybara.

GitOrigin-RevId: 92ea93d1b47a82667044bd0af05f7fdb34d2c2c2
2025-08-30 11:55:11 -07:00
ninjadrknss
96004f9bb5 [wpimath] Replace Pose2/3d.exp(Twist2/3d) with Pose2/3d.plus(Twist2/3d.exp()) (#8188)
This better matches math notation.
2025-08-30 11:37:09 -07:00
Ryan Blue
55e52bc2b7 ThirdPartyNotices: Update location of Simd library (NFC) (#8193) 2025-08-25 16:38:37 -06:00
ninjadrknss
c280fce147 [wpimath] Replace MathUtil.clamp() with Java 21 Math.clamp() (#8186) 2025-08-23 09:01:51 -07:00
Gold856
183328384b [upstream_utils] Remove extra protobuf patch (#8181)
Missed in the rebase for #7988
2025-08-17 18:41:51 -07:00
Gold856
d4311d5a29 [wpiutil,glass,dlt] Replace libprotobuf with upb for dynamic decode (#7988)
libprotobuf is a very annoying dependency to deal with, and with the switch to nanopb for generated C++ code, libprotobuf is only used for dynamic decode in the GUI apps. libprotobuf has been swapped out with upb, a much smaller C-based library that supports reflection and can therefore do dynamic decode. This means we can remove the libprotobuf dependency and stop dealing with build issues because of it.
2025-08-16 22:56:32 -07:00
Ryan Blue
7f35104012 [hal] AddressableLED: Restore alternative color order support (#8130)
Unlike armv7, aarch64 doesn't have alignment assertions for SIMD instructions. The compiler output between the aligned and unaligned variants is the same.
2025-08-16 22:53:04 -07:00
Thad House
73c26dcf89 [hal] Enable CAN FD on bus when bus is configured for FD (#8175)
Without this, FD reads would not be supported, however FD writes would still work.
2025-08-16 22:51:45 -07:00
Peter Johnson
f89cf2e441 Merge branch 'main' into 2027 2025-08-09 00:55:56 -07:00
Peter Johnson
3e4e9c9b01 Merge branch 'main' into 2027 2025-08-09 00:55:26 -07:00
DeltaDizzy
94399dd7e0 [datalog] Overload DataLogReaderThread::GetEntry to accept an entry id (#8152)
It is useful when programmatically interacting with DataLogs to be able to retrieve an record's associated metadata (entry name, type, and metadata string), but the only reference to an entry that a record contains is the id. DataLogReaderThread already builds a map of id->DataLogReaderEntry, but it was unexposed until now.
2025-08-08 09:09:54 -06:00
Austin Schuh
6eba91bc04 [bazel] Build and test wpical with bazel (#8155)
This pulls down the prebuilt ceres libraries and uses them with Bazel to
build and test wpical.

Do note that bazel looks up artifacts used for testing differently than
the other build systems.  It wants you to use its runfiles API to find
the dependencies reliably.  Add a function to look up the paths for
files, and use runfiles only when building with Bazel to maintain
compatibility with other languages.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-06 22:56:42 -06:00
Austin Schuh
c01f0c3d46 [bazel] Use rules_jvm_external for opencv (#8158)
This makes it so rules_jvm_external also doesn't package up all the
opencv class files into the final published packages.  And is simpler to
maintain.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-06 22:16:44 -06:00
Austin Schuh
d203541b7b [bazel] Build wpilibj docs too (#8159)
We've got javadocs for each module, but wpilib has 1 for everything.
Build that too using rules_jvm_external.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-05 20:54:59 -06:00
Austin Schuh
13852760f6 [bazel] Build wpilibc docs with doxygen (#8157)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-04 23:49:47 -06:00
Austin Schuh
dffa2542c9 [build] Fix wpiformat errors in 2027 (#8156)
Must have gotten introduced in a merge, let's fix them.
2025-08-03 21:36:05 -06:00
Peter Johnson
ed7982563b Merge branch 'main' into 2027 2025-08-03 11:51:25 -07:00
Austin Schuh
23f5725853 [bazel] Reduce number of windows builds in CI (#8145)
Windows is proving to be a *lot* slower than everything else.  I supect
this is because we are building both arm64 and x86 every time, which
ends up being twice the work.  Leave those builds in place, but skip
doing them in CI.  This should be a 2x speedup when building Windows
code.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-02 23:09:29 -07:00
James Kuszmaul
3b7d0e7bf5 [readme] Mention X11-dev dependency in developer README (#8150)
Tested a bazel build of //... on a (relatively) clean Debian system.

It feels like there is a more pricnipled list we could provide (e.g.,
libglfw3-dev), although I think most of those would also end up
installing more than necessarily required.
2025-08-02 23:08:58 -07:00
Austin Schuh
0dd77e55cb [bazel] Build and publish ntcoreffi too (#8147) 2025-08-02 21:59:50 -07:00
Austin Schuh
1ab705b354 [bazel] Publish ntcore + wpimath for real (#8144)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-01 22:12:38 -07:00
Austin Schuh
dbf22c98b4 [bazel] Build processstarter with bazel too (#8143) 2025-08-01 22:12:16 -07:00
Austin Schuh
9c523f98f5 [bazel] Build and deploy binary tools (#8142)
This is a subset of the full publish review.
2025-08-01 17:20:43 -07:00
Austin Schuh
0089e518cb [bazel] Update configuration for which platforms to split debug symbols for (#8140)
I could have sworn that we were only splitting debug symbols on x86.
The most recent tests I have done suggest that is backwards.  This is
easy to reconfigure later if needed.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-01 13:00:13 -07:00
Austin Schuh
2f918900ff [bazel] Publish wpilib{c,j}examples (#8136)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-01 12:59:52 -07:00
Austin Schuh
48077cdb91 [bazel] Add publishing rules for other package types (#8139)
This adds shortcuts for static only, shared only, and binary projects.
The end result is that it is pretty easy from here to publish all the
arifacts needed.
2025-08-01 12:59:25 -07:00
Austin Schuh
6e9ed8b7f1 [bazel] Rename imgui so it deploys correctly (#8137)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-08-01 12:58:58 -07:00
Gold856
e0e774abde [commands, wpimath] Remove Mecanum/SwerveControllerCommand and HolonomicDriveController (#8119) 2025-07-31 23:05:42 -07:00
Austin Schuh
b251d16ef7 [bazel] Build simulation/halsim_ws_client shared+static libraries (#8138)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-31 23:04:16 -07:00
Austin Schuh
76881e2940 [bazel] Rename liblibglass -> libglass (#8135)
The lib gets automatically added, don't add it by hand.
2025-07-31 23:03:19 -07:00
Austin Schuh
2245dccff6 [bazel] Link cscorestatic correctly (#8134)
Statically link opencv, and make it work on all the architectures.
2025-07-31 23:02:56 -07:00
Austin Schuh
12223ff188 [bazel] Implement cscore shared libraries (#8089)
Use all our fancy new linking code to link cscore properly. nm reports that the symbols look quite good.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Co-authored-by: PJ Reiniger <pj.reiniger@gmail.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2025-07-31 21:04:22 -07:00
Austin Schuh
79f6351073 [bazel] Link all third party apriltag symbols (#8132)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-31 19:27:35 -07:00
Austin Schuh
ef95333a89 [bazel] Shorten the platform suffix for windows arm (#8129)
This needs to be short enough to fit in the windows path restriction.
The full name was too long for some targets.
2025-07-31 19:27:21 -07:00
Austin Schuh
343e748643 [bazel] Upgrade opencv and toolchains dependencies (#8128)
This sets us up to properly depend on opencv, by introducing the new
helpers, @rules_bzlmodrio_toolchains//cc:cc_shared_import.bzl to import
the shared libraries correctly from opencv.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-30 21:30:00 -07:00
Austin Schuh
db95f55394 [bazel] Handle debug symbols and debug builds like gradle (#8118)
This follows the gradle build accurately.  Gradle copies debug symbols
into a second file (libfoo.so.debug) and links it back into the .so
file.  Disable this behavior when gradle doesn't do it today.

Also, name everything correctly.  When building debug builds, most
libraries get a 'd' at the end of them.  Do that here too.
2025-07-30 20:07:06 -07:00
Austin Schuh
3871cab6e8 [bazel] Use WPILIB_VERSION automatically for version template generation (#8117) 2025-07-26 15:10:15 -07:00
Gold856
22ed224035 [build] Clean up DataLog CMake build (#8116) 2025-07-26 14:48:58 -07:00
Sam Carlberg
8d36df671b [wpiunits] Make Java units immutable only (#8115)
Remove mutable implementations, as systemcore doesn't need mutability to keep performance under control.
2025-07-26 14:48:35 -07:00
Austin Schuh
373eedc77b [bazel] Generate a filtered .def file for windows for wpimath (#8113)
wpimath otherwise quickly gets too many symbols.  Instead, gradle
exports only some of the symbols from protobuf files automatically, and
then manually exports the math operations.  Do that here too.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-26 14:47:12 -07:00
Jade
e93c8cdb29 [build] Upgrade to Java 21 (#7547)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: sciencewhiz <sciencewhiz@users.noreply.github.com>
2025-07-25 15:45:40 -07:00
PJ Reiniger
c78bd942bb [bazel] Make publishers for c++ hdr / srcs, and java (#8114) 2025-07-24 09:11:09 -04:00
Austin Schuh
e2f901822c [bazel] Upgrade bazel to 8.3.1 and rules_cc to 0.1.4 (#8112) 2025-07-24 09:10:10 -04:00
Thad House
cd0e7fb7d7 [hal, wpilib] Fix LED key and byte order (#8110) 2025-07-22 17:45:10 -07:00
Austin Schuh
60098b0685 [bazel] Build a single maven artifact bundle (#8049)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Co-authored-by: PJ Reiniger <pj.reiniger@gmail.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2025-07-22 14:26:20 -06:00
Peter Johnson
f3af50fc8e [hal, wpilib] Update Addressable LED support (#8100) 2025-07-21 21:52:10 -07:00
Peter Johnson
8aa312fb6f Merge branch 'main' into 2027 2025-07-21 18:38:43 -07:00
sciencewhiz
09680072ac [examples] Add Snippets for Accelerometers using OnboardIMU (#8087) 2025-07-20 22:19:14 -07:00
Tyler Veness
946ab9e98f [wpimath] Fix S3UKF tests (#8097)
They weren't actually instantiating S3UKF.
2025-07-20 22:18:31 -07:00
sciencewhiz
5905a3ba27 [hal] Clean up HAL ports (#8101)
Remove references to PWM/Digital/relay Headers.
Fix analog cnd digital hannels
Set SPI and relay channels to 0.
Fix typos and roboRIO references.
2025-07-20 22:17:19 -07:00
sciencewhiz
b445d48033 [ci] Remove tools build (#8098) 2025-07-18 20:44:46 -07:00
Jonah Bonner
a6892b6cd5 [hal, wpilib] Add support for onboard IMU mount orientations with Euler angles (#8061) 2025-07-17 21:20:10 -07:00
Ryan Blue
fa65657746 [hal] Set publish options for SmartIO mode publisher (#8096) 2025-07-17 21:17:18 -07:00
PJ Reiniger
412d189507 [bazel] Set readonly api when building pull requests (#8094) 2025-07-16 20:45:14 -07:00
PJ Reiniger
b85a0d5cf3 [build] Add missing sources to published zip files (#8055)
Gradle publishing does not capture all of the source files that are used during a build. This should get most of them, and get it equivalent to what bazel pushes out.
2025-07-15 21:20:28 -07:00
Thad House
24e2d2d676 [hal] Remove DS Data timeout (#8083) 2025-07-15 21:18:17 -07:00
Joseph Eng
1530fccbd0 [wpimath] Implement Scaled Spherical Simplex Filter (S3F) (#8091)
Adds S3SigmaPoints based on MerweScaledSigmaPoints. In addition, restructures UnscentedKalmanFilter to support different sigma point generators and provides MerweUKF and S3UKF for convenience when working with either kind of filter.

S3UKFTest is copied from MerweUKFTest (which is a rename of UnscentedKalmanFilterTest). Curiously, however, in Java the original tolerance used in MerweUKFTest.testDriveConvergence() for the final rotation was too low for S3UKFTest, so the tolerance is increased from 0.000005 (5e-6) radians to 0.00015 (1.5e-4) radians. However, the C++ version still uses the original tolerance. (This difference is probably because Java uses a final rotation of 5.846 degrees while C++ uses a final rotation of 5.846 radians)

Closes #8072.

Breaking changes:

- (C++) UnscentedKalmanFilter has a new template parameter for the sigma point generator type.
- (Java) UnscentedKalmanFilter has an additional parameter to every constructor providing an instance of a sigma point generator.
- (C++) int MerweScaledSigmaPoints.NumSigmas() has been replaced with constexpr int MerweScaledSigmaPoints::NumSigmas.
- (C++) The second parameter of SquareRootUnscentedTransform has been changed from States to NumSigmas.
2025-07-15 21:17:25 -07:00
sciencewhiz
f03df5388e [doc] Clarify mount orientation for SystemCore IMU (NFC) (#8088) 2025-07-14 23:47:59 -07:00
sciencewhiz
efedc5d69a [doc] Update Analog Input voltage to 3.3v (NFC) (#8085) 2025-07-14 23:47:30 -07:00
Thad House
8d248f61fd [hal] Various CAN fixes (#8043) 2025-07-14 23:46:57 -07:00
Thad House
3497a7d09f [hal] Add frequency support to DutyCycle (#8076) 2025-07-14 23:46:17 -07:00
Thad House
ef24c1df97 [hal] Implement HAL_GetSystemTimeValid on SystemCore (#8051) 2025-07-14 23:45:28 -07:00
PJ Reiniger
abeaa40e38 [ci] Install bazel on docker image builds (#8079)
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2025-07-13 22:12:45 -05:00
Peter Johnson
7c1a22376b Merge branch 'main' into 2027 2025-07-13 20:11:31 -07:00
Ryan Blue
fa08679762 [wpiutil] Enable priority_mutex for systemcore (#8071) 2025-07-11 08:29:44 -05:00
Kevin-OConnor
72d7a07635 [upstream_utils] Update fmt to recent commit (#8077)
Fixes crash seen in SystemCore Java debugging.
2025-07-10 17:00:25 -05:00
PJ Reiniger
d36b1d0dd9 [bazel] Update bazel toolchain, libssh, opencv (#8075) 2025-07-08 20:20:08 -07:00
Peter Johnson
7142fb1f5f Merge branch 'main' into 2027 2025-07-08 19:54:03 -07:00
Austin Schuh
89c1fc156d [bazel] Switch build tag filters from no-roborio to target_compatible_with (#8067)
This uses platforms a lot better.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-07 23:44:08 -05:00
Jason Daming
708bf7d815 [wpilib] Add unit docs for return type of DCMotorSim getters (#8070)
We took away the units as part of the function name but didn't comment them back
2025-07-06 19:45:47 -07:00
Austin Schuh
d7550a02f7 [bazel] Upgrade and reorganize dependencies (#8063)
Pull in some of the fixes merged into the various dependencies.  While
we are here, move all the downloads up to the top of the file so they
download in parallel.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-07-05 18:59:22 -05:00
Thad House
f6558c4815 [hal] Update analog scaling for updated image (#8052) 2025-07-04 11:51:28 -07:00
Thad House
0304f50141 [wpiutil] Use steady_clock directly on SystemCore (#8053)
Don’t offset it from program start time.

This will make it much easier to coordinate timestamps across the system.

Closes #8041
2025-07-04 08:18:03 -05:00
Austin Schuh
44d42759a4 Work around gcc 12 overlap memory region warning (#8058)
Thad says this is a compiler bug, rather than fight it, let's use a new
code path through the compiler.

ERROR: /home/austin/local/allwpilib3/wpilibcExamples/BUILD.bazel:50:12: Compiling wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp failed: (Exit 1): gcc failed: error executing CppCompile command (from target //wpilibcExamples:I2CCommunication-test) /usr/bin/gcc -U_FORTIFY_SOURCE -fstack-protector -Wall -Wunused-but-set-parameter -Wno-free-nonheap-object -fno-omit-frame-pointer -g0 -O2 '-D_FORTIFY_SOURCE=1' -DNDEBUG -ffunction-sections ... (remaining 234 arguments skipped)

Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging
In file included from /usr/include/c++/12/ios:40,
                 from /usr/include/c++/12/istream:38,
                 from /usr/include/c++/12/sstream:38,
                 from /usr/include/c++/12/chrono:41,
                 from bazel-out/k8-opt/bin/wpilibc/_virtual_includes/wpilibc.static/frc/TimedRobot.h:7,
                 from bazel-out/k8-opt/bin/wpilibcExamples/_virtual_includes/I2CCommunication-examples-headers/Robot.h:10,
                 from wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp:5:
In static member function 'static constexpr std::char_traits<char>::char_type* std::char_traits<char>::copy(char_type*, const char_type*, std::size_t)',
    inlined from 'static constexpr void std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_S_copy(_CharT*, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /usr/include/c++/12/bits/basic_string.h:423:21,
    inlined from 'static constexpr void std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_S_copy(_CharT*, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /usr/include/c++/12/bits/basic_string.h:418:7,
    inlined from 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Allocator>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_M_replace(size_type, size_type, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /usr/include/c++/12/bits/basic_string.tcc:532:22,
    inlined from 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::assign(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /usr/include/c++/12/bits/basic_string.h:1647:19,
    inlined from 'constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]' at /usr/include/c++/12/bits/basic_string.h:815:28,
    inlined from 'virtual void Robot::RobotPeriodic()' at wpilibcExamples/src/main/cpp/examples/I2CCommunication/cpp/Robot.cpp:27:77:
/usr/include/c++/12/bits/char_traits.h:431:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets -4611686018427387902 and [-4611686018427387903, 4611686018427387904] may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
  431 |         return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
      |                                        ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
2025-07-03 22:58:47 -07:00
PJ Reiniger
1ccafdc13f [bazel] Simple shared library implementation (#8044) 2025-07-01 22:53:34 -07:00
crueter
6d92e2501d [wpimath] Remove unnecessary deprecation in ArmFeedForward (#8050)
Signed-off-by: crueter <swurl@swurl.xyz>
2025-06-30 21:13:54 -07:00
Peter Johnson
82ce317083 Merge branch 'main' into 2027 2025-06-30 20:21:11 -07:00
Joseph Eng
f55564729b [hal,wpilib,cmd] Update POVs to use enums (#7978) 2025-06-29 18:32:26 -07:00
PJ Reiniger
e13d237390 [bazel] Create better static libraries and add basic publishing (#8029) 2025-06-27 16:36:12 -07:00
Gold856
22b58c1853 [upstream_utils] Upgrade to LLVM 20.1.7 (#8033)
Also removes xxhash, Hashing, and MapVector to reduce the size of the patches and to speed up compile times by a smidge.
2025-06-24 22:36:22 -07:00
Jonah Bonner
a77441b78a [hal] Fix portrait yaw from onboard IMU (#8037) 2025-06-24 22:34:50 -07:00
Thad House
2361959ff1 [hal] Fix I2C order on systemcore (#8024) 2025-06-16 18:41:02 -07:00
Austin Schuh
6cfa21364e [bazel] Upgrade bazel to 8.2.1 (#8023)
This necesitated some rules updates too, and a couple of flag changes.

Signed-off-by: Austin Schuh <austin.linux@gmail.com>
2025-06-15 14:09:21 -07:00
Peter Johnson
3355383fe9 Merge branch 'main' into 2027 2025-06-13 22:26:09 -07:00
Thad House
fbee476fd2 [hal] Use new canbus names for systemcore (#8022)
The canbus names are changing to ensure stable ordering.
2025-06-13 21:30:42 -07:00
PJ Reiniger
fbbc4bc53c [bazel] Clean up bazel scripts (#7984) 2025-06-13 20:53:09 -07:00
Ryan Blue
5dfc664b93 [hal, wpilib] Add systemcore IMU (#8016) 2025-06-10 21:57:42 -07:00
Ryan Blue
89b97a21d8 [ntcore] Change 'null' to 'empty string' in NT StartServer docs (NFC) (#8017) 2025-06-10 21:55:51 -07:00
Peter Johnson
f99692f287 [ntcore] Minimize latency on localhost connections (#7997) 2025-06-02 16:43:18 -07:00
Thad House
2af8c59858 Replace /home/lvuser with /home/systemcore (#8002) 2025-06-02 16:42:56 -07:00
Thad House
4d74ea6278 [wpilib] Remove version writes (#8003)
This will use a much different mechanism in the future.
2025-06-02 16:41:47 -07:00
Thad House
a4cf2ea6ec [hal] Fix dutyCycle high time units (#8000)
The units are nanoseconds, not microseconds
2025-06-01 22:24:48 -07:00
Thad House
b205f3e1b4 [hal] Add temp value for HAL_GetUserVoltage3V3 (#8001)
Lots of higher level code in allwpilib depends on this, so we should return at least a valid and sane value, rather then erroring.
2025-06-01 22:23:51 -07:00
Thad House
be67432a5e [hal] Remove unnecessary print in CAN initialization (#8004) 2025-06-01 22:21:11 -07:00
DeltaDizzy
1955dcddb3 [wpimath] Fix SimpleMotorFeedforward no-accel overload returning negative voltage outputs (#7999)
SimpleMotorFeedforward::calculate(velocity) was not updated to account for the removal of calculate(velocity, acceleration), so it would pass currentVelocity = velocity and nextVelocity = 0, resulting in negative outputs in many scenarios.
2025-06-01 16:46:54 -07:00
Ryan Blue
e12d78a70a [ci] Deploy 2027 docs on tag (#7998) 2025-06-01 10:00:59 -07:00
Thad House
d3fbebc0a9 [hal] Add systemcore battery reading (#7995) 2025-05-31 10:53:11 -07:00
Thad House
1991af34a5 [hal] Update to new joystick protobuf definitions (#7991) 2025-05-31 10:52:27 -07:00
Peter Johnson
a6f601453a [examples] Fix up merge from main (#7994) 2025-05-30 17:36:53 -07:00
Peter Johnson
6c16e846fa Merge branch 'main' into 2027 2025-05-29 21:41:50 -07:00
sciencewhiz
25eacfa226 Update frcYear to 2027_alpha1 for vendordeps (#7989) 2025-05-27 08:19:03 -06:00
Thad House
22d12d2345 [wpinet] Add callback for mDNS service resolver (#7986) 2025-05-23 15:22:59 -05:00
Thad House
0cb4df7e05 [hal] Fix joystick buttons not working on SC (#7980) 2025-05-18 07:03:52 -07:00
Thad House
231ec348fe [hal] Update DS API to new format (#7977) 2025-05-16 22:15:14 -07:00
sciencewhiz
1596e2fd7a [ci] Remove dispatch for tools not supported in 2027 (#7975) 2025-05-15 18:25:34 -07:00
PJ Reiniger
6e3f48daeb [bazel] Add scripts to validate pregeneration tools (#7690) 2025-05-13 22:01:47 -07:00
PJ Reiniger
0e5a6f38d8 [bazel] MVP for building GUI related things with bazel (#7934)
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2025-05-12 08:24:41 -06:00
Peter Johnson
0a38400734 [datalog] Finish up a few missed moves from wpiutil split (#7956) 2025-05-04 11:21:29 -07:00
Thad House
08297430b5 [hal,wpilib] Add support for second I2C port (#7878) 2025-04-28 09:29:01 -06:00
Thad House
85a8fc9943 [hal] Add SystemCore to Java runtime type (#7932) 2025-04-28 09:10:32 -06:00
Peter Johnson
36811211be Merge branch 'main' into 2027 2025-04-25 23:45:43 -07:00
Gold856
2f0990e9d2 [commands] Remove control commands and subsystems (#7921) 2025-04-25 22:06:26 -07:00
Thad House
e2cc9e0059 [hal, wpilib] PWM Rewrite (#7845)
The HAL will only contain the output period and the raw microseconds. Higher level things such as SimDevice can handle everything else.
2025-03-20 19:23:22 -07:00
Thad House
2e21a41f87 [hal] Set number of SmartIO and CAN buses (#7871) 2025-03-19 19:49:54 -07:00
Thad House
52b353fe57 [hal, wpilib] Remove power rails that don't exist on systemcore (#7861) 2025-03-14 10:16:08 -07:00
Tyler Veness
d3cc185382 [upstream_utils] Upgrade to fmt 11.1.4 (#7852) 2025-03-04 18:50:56 -08:00
Tyler Veness
7cb29ce70b [datalog] Fix sorting of related header (#7832) 2025-03-02 10:47:48 -08:00
Thad House
baa20fa239 [hal, wpilib] Rewrite CAN APIs (#7798) 2025-02-25 19:07:01 -08:00
Gold856
b39744b562 [wpimath] Remove PathWeaver support (#7813)
Also rename file load type in glass to "Field Image JSON".
2025-02-21 07:43:45 -07:00
Thad House
72bba2491a [wpilib] Remove Nidec Brushless support (#7811) 2025-02-20 20:14:01 -08:00
Peter Johnson
98f933eca5 Merge branch 'main' into 2027 2025-02-20 00:26:23 -08:00
DeltaDizzy
da47f06d70 [datalog] Move all DataLog functionality to new datalog library (#7641)
Currently the major DataLog backend API (reading and writing) is split between wpiutil and glass. In the interest of allowing code that wants to use these APIs to not need to link to glass and declutter wpiutil, all of those APIs are moved to a new library named "datalog".

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-02-19 21:08:17 -08:00
Tyler Veness
ac1705ae2b [wpimath] Remove unit suffixes from variable names (#7529)
* Move units into API docs instead because suffixes make user code verbose and hard to read
* Rename trackWidth to trackwidth
* Make ultrasonic classes use meters instead of a mix of m, cm, mm, ft,
  and inches
2025-02-10 08:23:04 -07:00
Peter Johnson
764ada9b66 [hal] Change usage reporting to string-based (#7763) 2025-02-07 13:37:23 -07:00
Peter Johnson
bfff891b5c [cameraserver] Remove Axis camera functions (#7767) 2025-02-05 20:33:32 -08:00
Peter Johnson
35aee1d78d [hal] Add ntcore to other libs in styleguide (NFC) (#7766) 2025-02-05 20:32:51 -08:00
DeltaDizzy
297f0d1b03 [wpiutil] Change kInvalidFile to macro (#7750)
This is needed on Windows because accessing global variables across shared library boundaries doesn’t work.
2025-01-30 20:01:35 -07:00
Thad House
ad29d45dfb [hal] Remove HAL_GetPort (#7754) 2025-01-30 19:59:34 -07:00
Thad House
6e704370b3 [hal, wpilib] Remove DigitalSource and AnalogTrigger (#7753) 2025-01-30 19:58:21 -07:00
Tyler Veness
7533b323d1 [upstream_utils] Upgrade to fmt 11.1.3 (#7629) 2025-01-30 13:35:33 -07:00
Thad House
48ce2dcc8d [hal, wpilib] Add initial systemcore counter implementation (#7723) 2025-01-28 09:58:34 -07:00
Thad House
b799b285b3 [hal, wpilib] Remove digital source from encoder (#7740) 2025-01-28 06:43:09 -07:00
PJ Reiniger
3b345fe218 [bazel] Add macros to build jni code and java tests (#7693) 2025-01-26 16:52:33 -08:00
Peter Johnson
eee30c49e2 [wpilib] Remove LiveWindow (#7733)
This will be replaced by a different mechanism, but removing it eases
the initial implementation burden of a new Telemetry/Sendable framework.
2025-01-25 10:52:19 -08:00
Peter Johnson
adbe95e610 [wpilib] Remove Shuffleboard API (#7730) 2025-01-24 23:47:42 -08:00
Peter Johnson
01e71e73ce [build] developerRobot: Fix link order (#7732)
ntcore now needs to be after hal.
2025-01-24 23:47:09 -08:00
Thad House
5898cdd5c3 [hal, wpilib] Remove interrupt (#7724) 2025-01-23 21:45:18 -08:00
Thad House
e2b6beb28a [hal, wpilib] Remove DigitalGlitchFilter (#7725) 2025-01-23 21:44:18 -08:00
Thad House
5a6c895b87 [hal, wpilib] Remove built in accelerometer (#7702) 2025-01-17 14:06:09 -08:00
Thad House
1600e773f4 [hal, wpilib] Remove DMA (#7701) 2025-01-17 14:05:34 -08:00
Thad House
f80874dd4b [hal, wpilib] Remove analog accumulator and analog gyro (#7697)
The 2 high level classes were temporarily kept to keep the examples compiling. We will remove those when we have the interface into the built in IMU.
2025-01-17 12:58:31 -08:00
Thad House
92f0a3c961 [hal, wpilib] Remove SPI support (#7678) 2025-01-17 00:22:29 -08:00
Thad House
dc335ddedb [hal] Remove everything that references chipobject or VISA (#7698) 2025-01-16 23:21:40 -08:00
Thad House
ff1b2a205e [hal, wpilib] Remove analog output (#7696) 2025-01-16 23:20:44 -08:00
Thad House
5017393b3a [hal, wpilib] Remove relay (#7695) 2025-01-16 23:20:07 -08:00
Peter Johnson
d9f8fded09 Merge branch 'main' into 2027 2025-01-16 23:17:59 -08:00
Thad House
1cad4f64a4 [hal] Add high level way of getting systemserver NT instance (#7683) 2025-01-16 10:49:40 -07:00
Thad House
58cb395d76 [hal] Add systemcore duty cycle (#7682) 2025-01-15 12:57:31 -07:00
Thad House
24d6e87447 Remove CrossConn and Integration Tests (#7692) 2025-01-15 12:53:17 -07:00
Thad House
f81c42e700 [hal] Fix systemcore analog input scaling (#7691) 2025-01-15 12:52:57 -07:00
Thad House
fa71fb55a2 [wpilib] Remove I2C Warnings (#7677)
SystemCore won't have the I2C issues.
2025-01-14 12:31:47 -07:00
Thad House
45d7549ca9 [hal] Add systemcore analog input (#7681) 2025-01-14 12:30:44 -07:00
Thad House
afbaa43539 [wpiutil] Remove roboRIO-specific timestamp code (#7669) 2025-01-13 15:59:11 -07:00
Thad House
e41b33960a [rtns] Remove roboRIO team number setter (#7667)
It won't be needed in 2027.
2025-01-13 12:26:03 -07:00
Thad House
df77580a15 [hal] Remove athena hal folder (#7668)
Also remove roborio bazel target.
2025-01-13 12:25:28 -07:00
Thad House
09a6bc9a25 [build] Remove RoboRIO specializations from build system (#7670) 2025-01-13 12:23:54 -07:00
Thad House
666d1638ce [hal] Digital IO SystemCore implementation (#7621) 2025-01-12 16:58:46 -08:00
Peter Johnson
03d9e96877 [wpiutil] Change StringExtras split() to template (#7636)
It now calls back a function for each part rather than creating a SmallVector.
2025-01-05 20:53:43 -08:00
Peter Johnson
0f6693594c [glass] Split DataSource into type-specific variants (#7588) 2025-01-03 13:36:40 -08:00
Peter Johnson
148fcdca85 [wpiutil] DataLog: Move schema info to a separate map (#7626) 2025-01-03 13:28:50 -08:00
Peter Johnson
93521420c8 [wpinet] uv::AddrToName: Add StringAssignable constraint (#7627) 2025-01-03 13:28:29 -08:00
Peter Johnson
12a1475ee4 [wpiutil] Remove LEB128 (#7628)
This was only used for NT3.
2025-01-03 07:13:49 -08:00
Peter Johnson
1240ee1bf4 [ntcore] Remove NT3 support (#7625)
- Remove StartClient3
- Rename StartClient4 to StartClient
- Remove port3 parameter from StartServer
- Remove 3-suffix constants
- Remove 4 suffix from constants

Also remove Shuffleboard build from CI.
2025-01-02 23:05:13 -08:00
Peter Johnson
da90ffd24a [wpical] Disable systemcore target (#7620) 2025-01-01 16:30:23 -08:00
Peter Johnson
a931a6554f Merge branch 'main' into 2027 2024-12-29 18:22:39 -08:00
Peter Johnson
3232630a38 Merge branch 'main' into 2027 2024-12-27 19:40:43 -08:00
Tyler Veness
df244cd198 [wpimath] Clean up arm and elevator feedforward APIs (#7595) 2024-12-27 08:12:14 -06:00
Peter Johnson
2a757eaeb5 Merge branch 'main' into 2027 2024-12-26 18:55:43 -08:00
hjelstromboli
78b14c5204 [hal] Update SmartIO PWM implementation (#7571)
* Integer microsecond setters and getters
* Per-port subtables
2024-12-22 13:45:44 -06:00
Peter Johnson
529bab6ca1 Merge branch 'main' into 2027 2024-12-19 20:40:37 -08:00
Tyler Veness
03f0fc4dea [wpimath] Use immutable member functions in ChassisSpeeds (#7545) 2024-12-15 16:09:34 -08:00
Peter Johnson
945d416d07 Merge branch 'main' into 2027 2024-12-14 10:45:32 -08:00
Thad House
6ba7189373 [hal] Enable periodic CAN sends (#7530)
* Implement periodic can support

* Fix build
2024-12-08 22:29:01 -08:00
Thad House
31d1aa62c1 [hal] Fixes for making PWM drive (#7528)
* Increase connection timeout, fix ld path, properly initialize smart io.

* Also fix HAL_GetSystemTimeValid, so DataLogManager doesn't throw errors.
2024-12-08 22:08:05 -08:00
Thad House
b6ae9e9cc9 Merge branch 'main' into 2027 2024-12-08 12:04:23 -08:00
Thad House
f1e4eafaa0 [hal] Add initial PWM support for systemcore (#7525) 2024-12-08 12:02:22 -08:00
Thad House
41d4826694 [hal] Initial CAN implementation for SystemCore (#7514) 2024-12-08 12:01:28 -08:00
Tyler Veness
220f4e1ba4 [wpimath] Remove RamseteController and RamseteCommand (#7522) 2024-12-07 23:38:35 -08:00
Tyler Veness
ae44295024 Use std::bit_cast (#7492) 2024-12-07 23:02:09 -08:00
Tyler Veness
4910436b10 [wpimath] Remove LUTs from LTV controllers (#7521)
The Raspberry Pi 5 is fast enough that we no longer need it.
```
Running ./build/DAREBench
Run on (4 X 2400 MHz CPU s)
CPU Caches:
  L1 Data 64 KiB (x4)
  L1 Instruction 64 KiB (x4)
  L2 Unified 512 KiB (x4)
  L3 Unified 2048 KiB (x1)
Load Average: 0.47, 0.72, 0.45
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
-------------------------------------------------------------------------------
Benchmark                                     Time             CPU   Iterations
-------------------------------------------------------------------------------
DARE_WPIMath_Dynamic                       34.4 us         34.4 us        20315
DARE_WPIMath_NoPrecondChecks_Dynamic       21.7 us         21.7 us        32266
DARE_WPIMath_Static                        15.2 us         15.2 us        45878
DARE_WPIMath_NoPrecondChecks_Static        7.84 us         7.84 us        89316
DARE_SLICOT                                79.4 us         79.4 us         8789
DARE_Drake                                 34.9 us         34.9 us        20074
```
2024-12-07 23:00:15 -08:00
Tyler Veness
a7349f00ef [wpimath] Fix duplicate Rotation2d constructor (#7524) 2024-12-07 22:31:09 -08:00
Peter Johnson
e493da3486 Merge branch 'main' into 2027 2024-12-07 21:38:18 -08:00
Peter Johnson
32ba751e58 Merge branch 'main' into 2027 2024-12-07 21:30:28 -08:00
Tyler Veness
62a6a77bbf [wpimath] Add affine transformation constructors and getters to geometry API (#7509)
Fixes #7429.
2024-12-07 21:29:02 -08:00
Tyler Veness
c81bd0c909 [ci] Upgrade to Ubuntu 24.04 (#7496) 2024-12-07 21:20:48 -08:00
Tyler Veness
c497e4ec22 [wpimath] Fix SimpleFeedforward overload set (#7516) 2024-12-07 20:32:16 -08:00
Thad House
6dbff902fa [build] Remove athena completely from build (#7517) 2024-12-07 20:31:10 -08:00
Thad House
be72e0ecd8 Skip linux arm64 build on tools build (#7511) 2024-12-07 16:37:11 -08:00
Thad House
e69c5710b3 [hal] Add ErrorInfo support to systemcore DS (#7488) 2024-12-07 14:14:21 -08:00
Peter Johnson
52b33edcbd Merge branch 'main' into 2027 2024-12-07 14:11:47 -08:00
Thad House
c8900cadc3 Add SPARKmini to PWM support (#7504) 2024-12-07 00:48:20 -08:00
Thad House
5058b48dea [developerRobot] Switch to initial password and host for systemcore (#7503) 2024-12-06 23:19:48 -08:00
Tyler Veness
144e79a614 [wpimath] Remove Rotation2d value field (#7490)
It's not part of SO(2).
2024-12-06 21:00:09 -08:00
Thad House
38b09a6dfd [hal] Clean up systemcore notifier impl (#7487)
* Clean up systemcore notifier impl

* Formatting fixes

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-05 09:17:45 -08:00
Thad House
d7cd71589a [hal] Enable an I2C bus on systemcore (#7485) 2024-12-04 20:19:51 -08:00
Thad House
a954091ea2 [ci] Remove 32-bit Windows builds (#7475) 2024-12-01 17:45:04 -08:00
Thad House
bf653d9895 [hal] Add SystemServer DS support (#7466) 2024-12-01 17:05:07 -08:00
Thad House
5a9e0abe44 [ci] Build tools with systemcore image (#7472) 2024-12-01 17:03:51 -08:00
Peter Johnson
5cab27fdd5 Merge branch 'main' into 2027 2024-12-01 17:03:03 -08:00
Thad House
ce63770970 Merge branch 'main' into 2027 2024-12-01 12:40:50 -08:00
Thad House
c51f65bd4f [hal] Add initial SystemServer support (#7463) 2024-11-30 20:31:26 -08:00
Thad House
82132c3272 [hal] Initial SystemCore empty HAL (#7454) 2024-11-30 10:04:00 -08:00
Peter Johnson
847c3120d3 Merge branch 'main' into 2027 2024-11-30 00:35:00 -08:00
Peter Johnson
7ae4333c81 [ci] Publish on 2027 repositories 2024-11-29 23:52:22 -08:00
3527 changed files with 148772 additions and 256803 deletions

View File

@@ -1,18 +1,24 @@
try-import %workspace%/bazel_auth.rc
try-import %workspace%/user.bazelrc
common --noenable_bzlmod
common --noenable_bzlmod --enable_workspace
# Resolves to --config=linux on Linux, --config=macos on Mac, --windows on windows
common --enable_platform_specific_config
build --java_language_version=17
build --java_runtime_version=roboriojdk_17
build --tool_java_language_version=17
build --tool_java_runtime_version=remotejdk_17
# Make bazel 8 work for us.
common --enable_workspace
build --experimental_cc_static_library
build --experimental_cc_shared_library
build --java_language_version=21
build --java_runtime_version=remotejdk_21
build --tool_java_language_version=21
build --tool_java_runtime_version=remotejdk_21
test --test_output=errors
test --test_verbose_timeout_warnings
import %workspace%/shared/bazel/compiler_flags/sanitizers.rc
import %workspace%/shared/bazel/compiler_flags/base_linux_flags.rc
import %workspace%/shared/bazel/compiler_flags/linux_flags.rc
import %workspace%/shared/bazel/compiler_flags/osx_flags.rc
import %workspace%/shared/bazel/compiler_flags/roborio_flags.rc
@@ -29,6 +35,7 @@ build:build_java --test_tag_filters=allwpilib-build-java --build_tag_filters=all
build:build_cpp --test_tag_filters=+allwpilib-build-cpp --build_tag_filters=+allwpilib-build-cpp
build:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
test:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
common:skip_robotpy --test_tag_filters=-robotpy --build_tag_filters=-robotpy
# Build Buddy Cache Setup
build:build_buddy --bes_results_url=https://app.buildbuddy.io/invocation/
@@ -55,3 +62,14 @@ build:ci --config=build_buddy
build:ci --remote_download_minimal
build --build_metadata=REPO_URL=https://github.com/wpilibsuite/allwpilib.git
common --define="WPILIB_VERSION=2025.424242.3.1-unknown"
# List of artifact types to build in CI.
# Anything else gets skipped to speed up CI.
common:ci --repo_env="WPI_PUBLISH_CLASSIFIER_FILTER=headers,sources,linuxsystemcore,linuxsystemcoredebug,linuxsystemcorestatic,linuxsystemcorestaticdebug,linuxx86-64,linuxx86-64debug,linuxx86-64static,linuxx86-64staticdebug,osxuniversal,osxuniversaldebug,osxuniversalstatic,osxuniversalstaticdebug,windowsarm64,windowsarm64debug,windowsarm64static,windowsarm64staticdebug,windowsx86-64,windowsx86-64debug,windowsx86-64static,windowsx86-64staticdebug"
# The 2 configurations for windows are very slow to build each time.
# Instead, skip the cross transition for ARM on x86, and the reverse on x86.
common:ci_windows_x86 --repo_env="WPI_PUBLISH_CLASSIFIER_FILTER=headers,sources,linuxsystemcore,linuxsystemcoredebug,linuxsystemcorestatic,linuxsystemcorestaticdebug,linuxx86-64,linuxx86-64debug,linuxx86-64static,linuxx86-64staticdebug,osxuniversal,osxuniversaldebug,osxuniversalstatic,osxuniversalstaticdebug,windowsx86-64,windowsx86-64debug,windowsx86-64static,windowsx86-64staticdebug"
common:ci_windows_arm --repo_env="WPI_PUBLISH_CLASSIFIER_FILTER=headers,sources,linuxsystemcore,linuxsystemcoredebug,linuxsystemcorestatic,linuxsystemcorestaticdebug,linuxx86-64,linuxx86-64debug,linuxx86-64static,linuxx86-64staticdebug,osxuniversal,osxuniversaldebug,osxuniversalstatic,osxuniversalstaticdebug,windowsarm64,windowsarm64debug,windowsarm64static,windowsarm64staticdebug"

View File

@@ -1 +1 @@
7.3.1
8.4.1

2
.gitattributes vendored
View File

@@ -28,3 +28,5 @@
# Generated files
*/src/generated/** linguist-generated
*/robotpy_native_build_info.bzl linguist-generated
*/robotpy_pybind_build_info.bzl linguist-generated

View File

@@ -18,9 +18,6 @@ runs:
wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
shell: bash
- name: Regenerate hal
run: ./hal/generate_usage_reporting.py
shell: bash
- name: Regenerate ntcore
run: ./ntcore/generate_topics.py
@@ -56,6 +53,11 @@ runs:
./wpimath/generate_quickbuf.py --quickbuf_plugin protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
shell: bash
- name: Regenerate Commands v3
run: |
./commandsv3/generate_files.py --quickbuf_plugin protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
shell: bash
- name: Regenerate wpiunits
run: ./wpiunits/generate_units.py
shell: bash

View File

@@ -8,6 +8,7 @@ inputs:
runs:
using: "composite"
steps:
# Sets up build buddy when no secret is found for the API key. This is most likely because this is triggered from an action from a fork instead of the main allwpilib repo.
- name: Setup without key
env:
API_KEY: ${{ inputs.token }}
@@ -17,6 +18,16 @@ runs:
echo "No API key secret detected, will setup readonly cache"
echo "build:ci --config=build_buddy_readonly" > bazel_auth.rc
# Set up the readonly key only if this build is for a pull request. Push builds happen in the forks repository,
# so the user should set their own buildbuddy api keys up there. Only enabling it for PR's should reduce heavy
# and more random load on the cache.
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "Assuming this is a pull request from a fork. Setting up the readonly api key"
echo "build:ci --remote_header=x-buildbuddy-api-key=QIOV65PTW1tVal3AJbe7" >> bazel_auth.rc
else
echo "Not setting up readonly key for trigger ${{ github.event_name }} since this is not a pull request, it is most likely a forks push. See the buildbuddy setup guide in README-Bazel.md to set up caching on your fork"
fi
- name: Set with key
env:
API_KEY: ${{ inputs.token }}

3
.github/labeler.yml vendored
View File

@@ -33,9 +33,6 @@
'component: sysid':
- changed-files:
- any-glob-to-any-file: sysid/**
'component: teamnumbersetter':
- changed-files:
- any-glob-to-any-file: roborioteamnumbersetter/**
'component: wpilibc':
- changed-files:
- any-glob-to-any-file: wpilibc/**

View File

@@ -12,8 +12,8 @@ jobs:
fail-fast: false
matrix:
include:
- { name: "Windows (native)", os: windows-2022, action: "test", config: "--config=windows", }
- { name: "Windows (arm)", os: windows-2022, action: "build", config: "--config=windows_arm", }
- { name: "Windows (native)", os: windows-2022, action: "test", config: "--config=ci_windows_x86", }
- { name: "Windows (arm)", os: windows-2022, action: "build", config: "--config=windows_arm --config=ci_windows_arm", }
name: "Build ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
@@ -22,8 +22,8 @@ jobs:
with: { fetch-depth: 0 }
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: 'temurin'
java-version: 21
architecture: x64
- id: Setup_build_buddy
@@ -32,7 +32,7 @@ jobs:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: bazel ${{ matrix.action }}
run: bazel --output_user_root=C:\\bazelroot ${{ matrix.action }} -k ... --config=ci ${{ matrix.config }} --verbose_failures
run: bazel ${{ matrix.action }} -k ... --config=ci ${{ matrix.config }} --verbose_failures
shell: bash
build-mac:
@@ -42,13 +42,18 @@ jobs:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
- id: Setup_build_buddy
uses: ./.github/actions/setup-build-buddy
with:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- name: bazel test (release)
run: bazel test -k ... --config=ci -c opt --config=macos --nojava_header_compilation --verbose_failures
run: bazel test -k ... --config=ci -c opt --nojava_header_compilation --verbose_failures
shell: bash
build-linux:
@@ -56,10 +61,10 @@ jobs:
fail-fast: false
matrix:
include:
- { name: "Linux (native)", os: ubuntu-22.04, action: "test", config: "--config=linux", }
- { name: "Linux (roborio)", os: ubuntu-22.04, action: "build", config: "--config=roborio", }
- { name: "Linux", os: ubuntu-24.04, container: "wpilib/systemcore-cross-ubuntu:2027-24.04", action: "test", config: "", }
name: "${{ matrix.name }}"
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
@@ -70,12 +75,18 @@ jobs:
with:
token: ${{ secrets.BUILDBUDDY_API_KEY }}
- uses: bazel-contrib/setup-bazel@0.15.0
with:
bazelisk-cache: true
repository-cache: true
bazelisk-version: 1.x
- name: bazel ${{ matrix.action }} (release)
run: bazel ${{ matrix.action }} ... --config=ci -c opt ${{ matrix.config }} -k --verbose_failures
buildifier:
name: "buildifier"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Set up Go 1.15.x
uses: actions/setup-go@v5

View File

@@ -16,10 +16,10 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
name: Android Arm64
abi: arm64-v8a
- os: ubuntu-22.04
- os: ubuntu-24.04
name: Android X64
abi: "x86_64"
@@ -36,7 +36,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
java-version: 21
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.9
@@ -45,7 +45,7 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: configure
run: cmake --preset with-sccache -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_WPILIB=OFF -DWITH_GUI=OFF -DWITH_CSCORE=OFF -DWITH_TESTS=OFF -DWITH_SIMULATION_MODULES=OFF -DWITH_PROTOBUF=OFF -DWITH_JAVA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake -DANDROID_ABI="${{ matrix.abi }}" -DANDROID_PLATFORM=android-24
run: cmake --preset with-sccache -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_WPILIB=OFF -DWITH_GUI=OFF -DWITH_CSCORE=OFF -DWITH_TESTS=OFF -DWITH_SIMULATION_MODULES=OFF -DWITH_JAVA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake -DANDROID_ABI="${{ matrix.abi }}" -DANDROID_PLATFORM=android-24
env:
SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}

View File

@@ -16,14 +16,13 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
name: Linux
container: wpilib/roborio-cross-ubuntu:2025-22.04
container: wpilib/systemcore-cross-ubuntu:2027-24.04
flags: "--preset with-java-and-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
- os: macOS-14
name: macOS
container: ""
env: ""
flags: "--preset with-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
- os: windows-2022
name: Windows
@@ -36,11 +35,11 @@ jobs:
steps:
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java libprotobuf-dev protobuf-compiler ninja-build
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java ninja-build
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install opencv protobuf@29 ninja
run: brew install opencv ninja
- uses: ilammy/msvc-dev-cmd@v1.13.0
if: runner.os == 'Windows'

View File

@@ -19,20 +19,20 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2025-22.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
- container: wpilib/systemcore-cross-ubuntu:2027-24.04
artifact-name: SystemCore
build-options: "-Ponlylinuxsystemcore"
- container: wpilib/raspbian-cross-ubuntu:2027-bookworm-24.04
artifact-name: Arm32
build-options: "-Ponlylinuxarm32"
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
- container: wpilib/aarch64-cross-ubuntu:2027-bookworm-24.04
artifact-name: Arm64
build-options: "-Ponlylinuxarm64"
- container: wpilib/ubuntu-base:22.04
- container: wpilib/systemcore-cross-ubuntu:2027-24.04
artifact-name: Linux
build-options: "-Ponlylinuxx86-64"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
steps:
- name: Free Disk Space
@@ -50,7 +50,7 @@ jobs:
fetch-depth: 0
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
if: startsWith(github.ref, 'refs/tags/v2027')
- name: Build with Gradle
uses: addnab/docker-run-action@v3
with:
@@ -103,12 +103,6 @@ jobs:
architecture: aarch64
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
artifact-name: Win32FFI
architecture: x86
task: ":ntcoreffi:build"
build-options: "-Pntcoreffibuild \"-Dorg.gradle.jvmargs=-Xmx1096m\""
outputs: "ntcoreffi/build/outputs"
- os: windows-2022
artifact-name: Win64FFI
architecture: x64
@@ -125,7 +119,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
java-version: 21
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
uses: wpilibsuite/import-signing-certificate@v2
@@ -135,19 +129,16 @@ jobs:
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
- name: Set Keychain Lock Timeout
run: security set-keychain-settings -lut 21600
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
shell: bash
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
- name: Set Java Heap Size
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
if: matrix.artifact-name == 'Win32'
if: startsWith(github.ref, 'refs/tags/v2027')
- name: Check disk free space (Windows)
run: wmic logicaldisk get caption, freespace
if: matrix.os == 'windows-2022'
@@ -175,7 +166,7 @@ jobs:
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
if: |
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027')))
- name: Check disk free space (Windows)
run: wmic logicaldisk get caption, freespace
if: matrix.os == 'windows-2022'
@@ -189,7 +180,7 @@ jobs:
build-documentation:
name: "Build - Documentation"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
steps:
- uses: actions/checkout@v4
@@ -198,10 +189,10 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
java-version: 21
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
if: startsWith(github.ref, 'refs/tags/v2027')
- name: Build with Gradle
run: ./gradlew docs:zipDocs --build-cache -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
env:
@@ -215,7 +206,7 @@ jobs:
publish:
name: "Documentation - Publish"
runs-on: ubuntu-22.04
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
needs: [build-documentation]
concurrency: ci-docs-publish
steps:
@@ -249,6 +240,11 @@ jobs:
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=release" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
- name: Set environment variables (2027)
run: |
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
echo "BRANCH=2027" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '2027')
- name: Install SSH Client 🔑
uses: webfactory/ssh-agent@v0.9.0
with:
@@ -277,12 +273,12 @@ jobs:
combine:
name: Combine
needs: [build-docker, build-host, build-documentation]
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Free Disk Space
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
@@ -295,48 +291,48 @@ jobs:
- uses: actions/checkout@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
with:
repository: wpilibsuite/build-tools
- uses: actions/download-artifact@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
with:
path: combiner/products/build/allOutputs
- name: Flatten Artifacts
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
- name: Check version number exists
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
run: |
cat combiner/products/build/allOutputs/version.txt
test -s combiner/products/build/allOutputs/version.txt
- uses: actions/setup-java@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
with:
distribution: 'temurin'
java-version: 17
- name: Combine (Main)
java-version: 21
- name: Combine (2027)
if: |
github.repository == 'wpilibsuite/allwpilib' &&
github.ref == 'refs/heads/main'
run: cd combiner && ./gradlew publish -Pallwpilib
github.ref == 'refs/heads/2027'
run: cd combiner && ./gradlew publish -Pallwpilib -Pbuild2027
env:
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- name: Combine (Release)
- name: Combine (2027 Release)
if: |
github.repository == 'wpilibsuite/allwpilib' &&
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
startsWith(github.ref, 'refs/tags/v2027')
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish -Pbuild2027
env:
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
@@ -344,25 +340,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: |
github.repository == 'wpilibsuite/allwpilib' &&
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
(github.ref == 'refs/heads/2027' || startsWith(github.ref, 'refs/tags/v2027'))
with:
name: Maven
path: ~/releases
dispatch:
name: dispatch
needs: [combine]
strategy:
matrix:
repo: ['SmartDashboard', 'PathWeaver', 'Shuffleboard', 'RobotBuilder']
runs-on: ubuntu-22.04
steps:
- uses: peter-evans/repository-dispatch@v3
if: |
github.repository == 'wpilibsuite/allwpilib' &&
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
with:
token: ${{ secrets.TOOL_REPO_ACCESS_TOKEN }}
repository: wpilibsuite/${{ matrix.repo }}
event-type: tag
client-payload: '{"package_name": "allwpilib", "package_version": "${{ github.ref_name }}"}'

View File

@@ -20,7 +20,7 @@ jobs:
wpiformat:
name: "wpiformat"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -59,9 +59,9 @@ jobs:
tidy:
name: "clang-tidy"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
container: wpilib/ubuntu-base:22.04
container: wpilib/ubuntu-base:24.04
steps:
- uses: actions/checkout@v4
with:
@@ -92,9 +92,9 @@ jobs:
run: ${{ runner.temp }}/wpiformat/bin/wpiformat -no-format -tidy-changed -compile-commands=build/TargetedCompileCommands/linuxx86-64debug -vv
javaformat:
name: "Java format"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
container: wpilib/ubuntu-base:22.04
container: wpilib/systemcore-cross-ubuntu:2027-24.04
steps:
- uses: actions/checkout@v4
with:

View File

@@ -13,7 +13,7 @@ concurrency:
jobs:
update:
name: "Update"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:

View File

@@ -33,7 +33,7 @@ jobs:
container: wpilib/roborio-cross-ubuntu:2025-24.04
steps:
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-17 libprotobuf-dev protobuf-compiler ninja-build
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv-java clang-18 ninja-build
- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.9
@@ -41,7 +41,7 @@ jobs:
- uses: actions/checkout@v4
- name: configure
run: mkdir build && cd build && cmake -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-17 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-17 -DWITH_JAVA=OFF ${{ matrix.cmake-flags }} ..
run: mkdir build && cd build && cmake -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-18 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-18 -DWITH_JAVA=OFF ${{ matrix.cmake-flags }} ..
env:
SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}

View File

@@ -23,20 +23,20 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2025-22.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
- container: wpilib/systemcore-cross-ubuntu:2027-24.04
artifact-name: SystemCore
build-options: "-Ponlylinuxsystemcore"
- container: wpilib/raspbian-cross-ubuntu:2027-bookworm-24.04
artifact-name: Arm32
build-options: "-Ponlylinuxarm32"
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
- container: wpilib/aarch64-cross-ubuntu:2027-bookworm-24.04
artifact-name: Arm64
build-options: "-Ponlylinuxarm64"
- container: wpilib/ubuntu-base:22.04
- container: wpilib/systemcore-cross-ubuntu:2027-24.04
artifact-name: Linux
build-options: "-Ponlylinuxx86-64"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
steps:
- name: Free Disk Space
@@ -102,12 +102,6 @@ jobs:
architecture: aarch64
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
artifact-name: Win32FFI
architecture: x86
task: ":ntcoreffi:build"
build-options: "-Pntcoreffibuild \"-Dorg.gradle.jvmargs=-Xmx1096m\""
outputs: "ntcoreffi/build/outputs"
- os: windows-2022
artifact-name: Win64FFI
architecture: x64
@@ -124,7 +118,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
java-version: 21
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
uses: wpilibsuite/import-signing-certificate@v2
@@ -138,9 +132,6 @@ jobs:
run: security set-keychain-settings -lut 21600
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
- name: Set Java Heap Size
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
if: matrix.artifact-name == 'Win32'
- name: Check disk free space (Windows)
run: wmic logicaldisk get caption, freespace
if: matrix.os == 'windows-2022'

View File

@@ -1,196 +0,0 @@
name: Tools
on: [pull_request, push]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
YEAR: 2025
jobs:
build-artifacts:
name: "Build - WPILib"
runs-on: ubuntu-22.04
env:
DISPLAY: ':10'
steps:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: false
docker-images: false
swap-storage: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gradle/actions/wrapper-validation@v4
- name: Build WPILib with Gradle
uses: addnab/docker-run-action@v3
with:
image: wpilib/roborio-cross-ubuntu:2025-22.04
options: -v ${{ github.workspace }}:/work -w /work -e GITHUB_REF -e CI -e DISPLAY
run: df . && rm -f semicolon_delimited_script && ./gradlew :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish :simulation:halsim_ws_server:publish :simulation:halsim_ws_client:publish :simulation:halsim_xrp:publish :fieldImages:publish :romiVendordep:publish :xrpVendordep:publish :epilogue-processor:publish :epilogue-runtime:publish :thirdparty:googletest:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
- uses: actions/upload-artifact@v4
with:
name: MavenArtifacts
path: |
development
retention-days: 1
Robotbuilder:
name: "Build - RobotBuilder"
needs: [build-artifacts]
runs-on: ubuntu-22.04
env:
DISPLAY: ':10'
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/robotbuilder
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- name: Patch RobotBuilder to use local development
run: cd src/main/resources/export && echo "wpi.maven.useLocal = false" >> java/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> java/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.maven.useLocal = false" >> cpp/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> cpp/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> cpp/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> cpp/build.gradle
- name: Install and run xvfb
run: sudo apt-get update && sudo apt-get install -y xvfb && Xvfb $DISPLAY &
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Build RobotBuilder with Gradle
run: ./gradlew build test --tests 'robotbuilder.exporters.*' -x htmlSanityCheck -PbuildServer -PreleaseMode ; cat build/test-results/test/TEST-robotbuilder.exporters.*.xml ;
- name: Summarize RobotBuilder Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: |
build/test-results/test/TEST*.xml
check_run: false
comment_mode: off
- uses: actions/upload-artifact@v4
if: always()
with:
name: RobotBuilderTestResults
path: |
build/reports/
- uses: actions/upload-artifact@v4
with:
name: RobotBuilder Build
path: |
build/libs/
retention-days: 7
Shuffleboard:
name: "Build - Shuffleboard"
needs: [build-artifacts]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/shuffleboard
fetch-depth: 0
- name: Patch Shuffleboard to use local development
run: sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" app/app.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/cameraserver/cameraserver.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/networktables/networktables.gradle
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- name: Install dependencies
run: sudo apt-get install -y libgtk2.0-0
- name: Build with Gradle
run: ./gradlew build -x Javadoc
- uses: actions/upload-artifact@v4
with:
name: Shuffleboard Build
path: |
build/allOutputs/
retention-days: 7
PathWeaver:
name: "Build - PathWeaver"
needs: [build-artifacts]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/PathWeaver
fetch-depth: 0
- name: Patch PathWeaver to use local development
run: sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" dependencies.gradle
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- name: Build with Gradle
run: ./gradlew build
- uses: actions/upload-artifact@v4
with:
name: PathWeaver Build
path: |
build/allOutputs/
retention-days: 7
# Robotpy:
# name: "Build - Robotpy"
# needs: [build-artifacts]
# runs-on: ubuntu-22.04
# steps:
# - uses: actions/checkout@v4
# with:
# repository: robotpy/mostrobotpy
# fetch-depth: 0
# - uses: actions/download-artifact@v4
# with:
# name: MavenArtifacts
# - name: Move artifacts
# run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
#
# - uses: actions/setup-python@v5
# with:
# python-version: 3.13
#
# - name: Install sccache
# uses: mozilla-actions/sccache-action@v0.0.9
#
# - name: Install deps
# shell: bash
# run: |
# python -m pip --disable-pip-version-check install -r rdev_requirements.txt
#
# - name: Install numpy (needed for stubgen but broken in raspbian CI)
# shell: bash
# run: |
# python -m pip --disable-pip-version-check install numpy
# - name: Patch RobotPy rdev to use local development
# run: git config user.name github-actions && git config user.email github-actions@github.com && set -- ~/releases/maven/development/edu/wpi/first/wpiutil/wpiutil-cpp/*/ ; wpilibversion=$(basename $1) && echo $wpilibversion && sed --regexp-extended -i 's@(wpilib_bin_url =).*@\1 \"file:\/\/'"$HOME"'\/releases\/maven\/development"@' rdev.toml && sed --regexp-extended -i 's/(wpilib_bin_version =).*/\1 \"'"$wpilibversion"'\"/' rdev.toml && ./rdev.sh update-pyproject --commit
# - name: Build + test wheels
# shell: bash
# run: |
# ./rdev.sh ci run
# env:
# RPYBUILD_STRIP_LIBPYTHON: "1"
# RPYBUILD_CC_LAUNCHER: sccache
# SCCACHE_WEBDAV_ENDPOINT: "https://frcmaven.wpi.edu/artifactory/wpilib-generic-cache-cmake-local"
# SCCACHE_WEBDAV_KEY_PREFIX: "sccache-robotpy"
# SCCACHE_WEBDAV_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
# SCCACHE_WEBDAV_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}

View File

@@ -13,7 +13,7 @@ concurrency:
jobs:
update:
name: "Update"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -120,12 +120,6 @@ jobs:
./mpack.py clone
./mpack.py copy-src
./mpack.py format-patch
- name: Run protobuf.py
run: |
cd upstream_utils
./protobuf.py clone
./protobuf.py copy-src
./protobuf.py format-patch
- name: Run sleipnir.py
run: |
cd upstream_utils
@@ -138,6 +132,12 @@ jobs:
./stb.py clone
./stb.py copy-src
./stb.py format-patch
- name: Run upb.py
run: |
cd upstream_utils
./upb.py clone
./upb.py copy-src
./upb.py format-patch
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output

View File

@@ -14,6 +14,7 @@ modifiableFileExclude {
thirdparty/
\.patch$
gradlew
BUILD.bazel
}
generatedFileExclude {
@@ -22,6 +23,13 @@ generatedFileExclude {
fieldImages/src/main/native/resources/
apriltag/src/test/resources/
wpilibc/src/generated/
apriltag/src/main/python/
apriltag/src/test/python/
wpilibc/src/main/python/
wpilibc/src/test/python/
xrpVendordep/src/main/python/
xrpVendordep/src/test/python/
}
repoRootNameOverride {
@@ -34,7 +42,6 @@ includeOtherLibs {
^cscore
^fmt/
^glass/
^google/
^gtest/
^hal/
^imgui
@@ -46,6 +53,7 @@ includeOtherLibs {
^support/
^units/
^unsupported/
^upb/
^vision/
^wpi/
^wpigui

128
BUILD.bazel Normal file
View File

@@ -0,0 +1,128 @@
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
load("@rules_pkg//:mappings.bzl", "pkg_files")
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
load("//shared/bazel/rules:publishing.bzl", "publish_all")
exports_files([
"LICENSE.md",
"ThirdPartyNotices.txt",
])
pkg_files(
name = "license_pkg_files",
srcs = [
"LICENSE.md",
"ThirdPartyNotices.txt",
],
visibility = ["//visibility:public"],
)
# bazel build //:requirements.lock
compile_pip_requirements(
name = "requirements",
extra_args = ["--allow-unsafe"],
requirements_in = "requirements.txt",
requirements_txt = "requirements_lock.txt",
requirements_windows = "//:requirements_windows_lock.txt",
# compile_pip_requirements does not respect target_compatible_with for some of the targets it generates under the hood
tags = ["no-systemcore"],
)
alias(
name = "quickbuf_protoc",
actual = select({
"@platforms//os:windows": "@quickbuffer_protoc_windows//file",
"@rules_bzlmodrio_toolchains//conditions:osx_aarch64": "@quickbuffer_protoc_osx_aarch64//file",
"@rules_bzlmodrio_toolchains//conditions:osx_x86_64": "@quickbuffer_protoc_osx_x86-64//file",
"@rules_bzlmodrio_toolchains//constraints/combined:is_linux": "@quickbuffer_protoc_linux//file",
}),
tags = ["pregeneration"],
visibility = ["//visibility:public"],
)
# This is a helper to run all of the pregeneration scripts at once.
write_source_files(
name = "write_all",
additional_update_targets = [
"//hal:write_hal",
"//ntcore:write_ntcore",
"//wpilibc:write_wpilibc",
"//wpilibcExamples:write_example_project_list",
"//wpilibj:write_wpilibj",
"//wpilibjExamples:write_example_project_list",
"//wpilibNewCommands:write_wpilib_new_commands",
"//commandsv3:write_commandsv3",
"//wpimath:write_wpimath",
"//wpiunits:write_wpiunits",
"//wpiutil:write_wpiutil",
],
tags = ["pregeneration"],
)
publish_all(
name = "publish",
targets = [
"//apriltag:apriltag-cpp_publish.publish",
"//apriltag:apriltag-java_publish.publish",
"//cameraserver:cameraserver-cpp_publish.publish",
"//cameraserver:cameraserver-java_publish.publish",
"//cscore:cscore-cpp_publish.publish",
"//cscore:cscore-java_publish.publish",
"//datalog:datalog-cpp_publish.publish",
"//datalog:datalog-java_publish.publish",
"//datalogtool:datalogtool_publish.publish",
"//docs:wpilibj_publish.publish",
"//epilogue-processor:processor-java_publish.publish",
"//epilogue-runtime:epilogue-java_publish.publish",
"//fieldImages:fieldImages-cpp_publish.publish",
"//fieldImages:fieldImages-java_publish.publish",
"//glass:glass-cpp_publish.publish",
"//glass:glassapp_publish.publish",
"//glass:glassnt-cpp_publish.publish",
"//hal:hal-java_publish.publish",
"//hal:wpiHal-cpp_publish.publish",
"//ntcore:ntcore-cpp_publish.publish",
"//ntcore:ntcore-java_publish.publish",
"//ntcoreffi:ntcoreffi-cpp_publish.publish",
"//outlineviewer:outlineviewer_publish.publish",
"//processstarter:processstarter_publish.publish",
"//romiVendordep:romiVendordep-cpp_publish.publish",
"//romiVendordep:romiVendordep-java_publish.publish",
"//simulation/halsim_ds_socket:halsim_ds_socket-cpp_publish.publish",
"//simulation/halsim_gui:halsim_gui-cpp_publish.publish",
"//simulation/halsim_ws_client:halsim_ws_client-cpp_publish.publish",
"//simulation/halsim_ws_core:halsim_ws_core-cpp_publish.publish",
"//simulation/halsim_ws_server:halsim_ws_server-cpp_publish.publish",
"//simulation/halsim_xrp:halsim_xrp-cpp_publish.publish",
"//sysid:sysid_publish.publish",
"//thirdparty/googletest:googletest-cpp_publish.publish",
"//thirdparty/imgui_suite:imguiSuite-cpp_publish.publish",
"//wpical:wpical_publish.publish",
"//wpigui:wpigui-cpp_publish.publish",
"//wpilibNewCommands:wpilibNewCommands-cpp_publish.publish",
"//wpilibNewCommands:wpilibNewCommands-java_publish.publish",
"//commandsv3:commandsv3-java_publish.publish",
"//wpilibc:wpilibc-cpp_publish.publish",
"//wpilibcExamples:commands_publish.publish",
"//wpilibcExamples:examples_publish.publish",
"//wpilibcExamples:templates_publish.publish",
"//wpilibj:wpilibj-java_publish.publish",
"//wpilibjExamples:commands_publish.publish",
"//wpilibjExamples:examples_publish.publish",
"//wpilibjExamples:templates_publish.publish",
"//wpimath:wpimath-cpp_publish.publish",
"//wpimath:wpimath-java_publish.publish",
"//wpinet:wpinet-cpp_publish.publish",
"//wpinet:wpinet-java_publish.publish",
"//wpiunits:wpiunits-java_publish.publish",
"//wpiutil:wpiutil-cpp_publish.publish",
"//wpiutil:wpiutil-java_publish.publish",
"//xrpVendordep:xrpVendordep-cpp_publish.publish",
"//xrpVendordep:xrpVendordep-java_publish.publish",
] + select({
"@platforms//cpu:x86_64": [
"//docs:wpilibc_publish.publish",
],
"//conditions:default": [],
}),
)

View File

@@ -90,7 +90,6 @@ option(WITH_EXAMPLES "Build examples" OFF)
option(WITH_TESTS "Build unit tests (requires internet connection)" ON)
option(WITH_GUI "Build GUI items" ON)
option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
option(WITH_PROTOBUF "Build protobuf support" ON)
option(WITH_BENCHMARK "Build the benchmark project" ON)
# Options for using a package manager (e.g., vcpkg) for certain dependencies.
@@ -151,14 +150,6 @@ endif()
find_package(LIBSSH CONFIG 0.7.1)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
if(WITH_PROTOBUF)
set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "" FORCE)
find_package(Protobuf REQUIRED)
find_program(PROTOC_COMPILER protoc REQUIRED)
endif()
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG OFF)
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
@@ -276,6 +267,9 @@ if(WITH_TESTS)
enable_testing()
add_subdirectory(thirdparty/googletest)
include(GoogleTest)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/catch2/extras")
add_subdirectory(thirdparty/catch2)
include(Catch)
endif()
if(USE_SYSTEM_LIBUV)
@@ -291,8 +285,11 @@ set(SELF_DIR "$\{SELF_DIR\}")
set(WPIUNITS_DEP_REPLACE_IMPL "find_dependency(wpiunits)")
set(WPIANNOTATIONS_DEP_REPLACE_IMPL "find_dependency(wpiannotations)")
set(WPIUTIL_DEP_REPLACE "find_dependency(wpiutil)")
set(DATALOG_DEP_REPLACE "find_dependency(datalog)")
add_subdirectory(wpiutil)
add_subdirectory(datalog)
if(WITH_NTCORE)
set(NTCORE_DEP_REPLACE "find_dependency(ntcore)")
set(WPINET_DEP_REPLACE "find_dependency(wpinet)")
@@ -330,7 +327,6 @@ if(WITH_GUI)
add_subdirectory(wpical)
endif()
if(LIBSSH_FOUND)
add_subdirectory(roborioteamnumbersetter)
add_subdirectory(datalogtool)
endif()
endif()
@@ -350,12 +346,14 @@ endif()
if(WITH_WPILIB)
set(APRILTAG_DEP_REPLACE "find_dependency(apriltag)")
set(COMMANDSV3_DEP_REPLACE "find_dependency(commandsv3)")
set(WPILIBC_DEP_REPLACE "find_dependency(wpilibc)")
set(WPILIBJ_DEP_REPLACE "find_dependency(wpilibj)")
set(WPILIBNEWCOMMANDS_DEP_REPLACE "find_dependency(wpilibNewCommands)")
add_subdirectory(apriltag)
add_subdirectory(wpilibj)
add_subdirectory(wpilibc)
add_subdirectory(commandsv3) # must be after wpilibj
add_subdirectory(wpilibNewCommands)
add_subdirectory(romiVendordep)
add_subdirectory(xrpVendordep)

View File

@@ -23,3 +23,32 @@ Examples:
build --local_ram_resources=HOST_RAM*.5 # Don't use more than half my RAM when building
build --local_cpu_resources=HOST_CPUS-1 # Leave one core alone
```
The default settings build all the release artifact variants relevant for your platform. The overall list of options ends up being, essentially, all the variants of (linux, osx, windows) x (debug, release) x (static, shared) x (aarch64, x86). OSX and Windows are hard to compile for from any other OS, so we by default build for your local OS and the system core, with all the variants.
This can be a bit expensive. If you would like to build a subset, you can specify the repo environmental variable, `WPI_PUBLISH_CLASSIFIER_FILTER`, and pick what you build for. The default is, in the .bazelrc file,
```
common --repo_env="WPI_PUBLISH_CLASSIFIER_FILTER=headers,sources,linuxsystemcore,linuxsystemcoredebug,linuxsystemcorestatic,linuxsystemcorestaticdebug,linuxx86-64,linuxx86-64debug,linuxx86-64static,linuxx86-64staticdebug,osxuniversal,osxuniversaldebug,osxuniversalstatic,osxuniversalstaticdebug,windowsarm64,windowsarm64debug,windowsarm64static,windowsarm64staticdebug,windowsx86-64,windowsx86-64debug,windowsx86-64static,windowsx86-64staticdebug"
```
Modify this to your likings if you want to build less.
## Pregenerating Files
allwpilib uses extensive use of pre-generating files that are later used to build C++ / Java libraries that are tracked by version control. Quite often,
these pre-generation scripts use some configuration file to create multipile files inside of an output directory. While this process could be accomplished
with a `genrule` that would require an explicit listing of every output file, which would be tedious to maintain as well as potentially confusing to people
adding new features those libraries. Therefor, we use `@aspect_bazel_lib` and their `write_source_files` feature to generate these directories. In the event that the generation process creates more than a small handful of predictable files, a custom rule is written to generate the directory.
## Remote Caching
One of the huge benefits of bazel is its remote caching ability. However, due to bazels strict build definitions it is hard to share remote cache artifacts between different computers unless our toolchains are fully hermetic, which means you are unlikely to be able to reuse the cache artifacts published from the `main` branch on your local machine like you might be able to with the `gradle` or `cmake` caches. Luckily the github actions CI machines are generally stable between runs and can reuse cache artifacts, and your local machine should remain stable, so if you set up a free buildbuddy account you can have your forks CI actions be able to use a personalized cache, as well as your local machine.
For the main `allwpilib` upstream, the cache is only updated on the main branch; pull requests from forks will not be able to modify the cache. However, you can set up your fork to enable its own cache by following the steps below.
### Setting Up API keys
Follow the [buildbuddy authentication](https://www.buildbuddy.io/docs/guide-auth) guide to create keys. For your local machine, it is recommended that you place the following configuration line in either a `user.bazelrc` or `bazel_auth.rc` file in the repositories root directory.
```
build --remote_header=<your api key>
```
To get your forks CI actions using your own buildbuddy cache, follow [GitHub's](https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/using-secrets-in-github-actions) documentation for setting up a repository secret. The secrets key should be `BUILDBUDDY_API_KEY`, and the value should be your buildbuddy API key.

View File

@@ -5,6 +5,7 @@ WPILib is normally built with Gradle, however for some systems, such as Linux ba
## Libraries that get built
* apriltag
* cameraserver
* commandsv3
* cscore
* fieldImages
* hal (simulation HAL only)
@@ -24,7 +25,6 @@ WPILib is normally built with Gradle, however for some systems, such as Linux ba
* datalogtool
* glass
* outlineviewer
* roborioteamnumbersetter
* sysid
* halsim_gui (if simulation extensions are enabled)
@@ -32,11 +32,9 @@ By default, all libraries get built with a default CMake setup. The libraries ar
## Prerequisites
The protobuf library and compiler are needed for protobuf generation.
OpenCV needs to be findable by CMake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build OpenCV from source and install it.
If you want JNI and Java, you will need a JDK of at least version 17 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
If you want JNI and Java, you will need a JDK of at least version 21 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
If you are building with unit tests or simulation modules, you will also need an Internet connection for the initial setup process, as CMake will clone google-test and imgui from GitHub.
@@ -66,8 +64,6 @@ The following build options are available:
* This option will build the HAL and wpilibc/j during the build. The HAL is the simulation HAL, unless the external HAL options are used. The CMake build has no capability to build for the roboRIO.
* `WITH_WPIMATH` (ON Default)
* This option will build the wpimath library. This option must be on to build wpilib.
* `WITH_PROTOBUF` (ON Default)
* This option will build with the protobuf library.
* `WITH_WPIUNITS` (`WITH_JAVA` Default)
* This option will build the wpiunits library. This option must be on to build the Java wpimath library and requires `WITH_JAVA` to also be on.
* `OPENCV_JAVA_INSTALL_DIR`
@@ -91,8 +87,6 @@ If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the `cmake
If you want, you can also use `ccmake` in order to visually set these properties as well. [Here](https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html) is the link to the documentation for that program. On Windows, you can use `cmake-gui` instead.
Note that if you are cross-compiling, you will need to override the protobuf options manually to point to the libraries for the target platform. Leave the protoc binary location as the path to the binary for the host platform, since protoc needs to execute on the host platform.
## Presets
The WPILib CMake setup has a variety of presets for common configurations and options used. The default sets the generator to Ninja and build directory to `build-cmake`. The other presets are `with-java` (sets `WITH_JAVA=ON`), `sccache` (sets the C/C++ compiler launcher to sccache), and `with-java-sccache` (a comibination of `with-java` and `sccache`.
@@ -119,7 +113,7 @@ sudo cmake --build . --target install
## Preparing to use the installed libraries
On Windows, make sure the directories for the libraries you built are on PATH. For wpilib, the default install location is `C:\Program Files (x86)\allwpilib`. If you built other libraries like OpenCV and protobuf from source, install them, and add the install directories to PATH. This ensures CMake can locate the libraries.
On Windows, make sure the directories for the libraries you built are on PATH. For wpilib, the default install location is `C:\Program Files (x86)\allwpilib`. If you built other libraries like OpenCV from source, install them, and add the install directories to PATH. This ensures CMake can locate the libraries.
You will also want to add the directories where the DLLs are located (usually the `bin` subdirectory of the install directory) to PATH so they can be loaded by your program. If you are using OpenCV and Java, the `opencv_java` DLL is located in either the `lib` subdirectory if you built but didn't install OpenCV, or the `java` subdirectory if you did install OpenCV.
@@ -235,6 +229,6 @@ Last Load Error:
C:\Program Files (x86)\allwpilib\bin\wpiHaljni.dll: Can't find dependent libraries
```
If you get this error, that's usually an indication that not all your libraries are in your PATH. The two libraries that should be in your PATH are OpenCV and protobuf. If the error is coming from cscore, it's likely you're missing OpenCV. Otherwise, it's likely you're missing protobuf.
If you get this error, that's usually an indication that not all your libraries are in your PATH. If the error is coming from cscore specifically, it's likely you're missing OpenCV. Otherwise, it's likely the wpilib libraries are not in a directory on PATH.
Note that Linux will not have this specific type of error, as it will usually tell you the dependent library you are missing. In that case, you most likely need to add the library to `LD_LIBRARY_PATH`.

13
README-RobotPy.md Normal file
View File

@@ -0,0 +1,13 @@
# robotpy in allwpilb
allwpilib hosts a mirror of RobotPy that can be built with bazel on Linux. The intent of the mirror is to have breaking changes identified early and fixed by the PR creator so that when wpilib releases are made there is much less work required to release a RobotPy version that wraps it. It is not a goal for allwpilib to replace the RobotPy repo; it will still be considered the "source of truth" for python builds and will be responsible for building against all of the applicable architectures and multiple versions of python.
## Build Process
The upstream RobotPy repository uses toml configuration files and semiwrap to produce Meson build scripts. The allwpilib fork uses these toml configuration files to auto generate bazel build scripts. In general, each project (wpiutil, wpimath, etc) defines two pybind extensions; one that simply wraps the native library, and another that adds extension(s) that and contains all of the python files for the library. Both of these subprojects have auto-generated build files; a `robotpy_native_build_info.bzl` for the lidar wraper and `robotpy_pybind_build_info.bzl` which defines the extensions and python library.
## Disabling robotpy builds
Building the robotpy software on top of the standard C++/Java software can result in more than doubling the amount of time it takes to compile. To skip building the robotpy tooling you can add `--config=skip_robotpy` to the command line or to your `user.bazelrc`
# Syncing with robotpy
NOTE: This process is currently unlanded while robotpy gets the 2027 branch stable
[Copybara](https://github.com/google/copybara) is used to maintin synchronization between the upstream robotpy repositories and the allwpilib mirror. Github actions can be manually run which will create pull requests that will update all of the robotpy files between the two repositories. The ideal process is that the allwpilib mirror is always building in CI, and once a release is created the RobotPy team can run the `wpilib -> robotpy` copybara task, make any fine tuned adjustements and create their release. In the event that additional changes are made on the robotpy side, they can run the `robotpy -> wpilib` task to push the updates back to the mirror. However the goal of the mirroring the software here is to be able to more rapidly test changes and will hopefully overwhelmingly eliminate the need for syncs this direction.

View File

@@ -42,11 +42,11 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
## Requirements
- [JDK 17](https://adoptium.net/temurin/releases/?version=17)
- [JDK 21](https://adoptium.net/temurin/releases/?version=21)
- Note that the JRE is insufficient; the full JDK is required
- On Ubuntu, run `sudo apt install openjdk-17-jdk`
- On Windows, install the JDK 17 .msi from the link above
- On macOS, install the JDK 17 .pkg from the link above
- On Ubuntu, run `sudo apt install openjdk-21-jdk`
- On Windows, install the JDK 21 .msi from the link above
- On macOS, install the JDK 21 .pkg from the link above
- C++ compiler
- On Linux, install GCC 11 or greater
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
@@ -56,9 +56,16 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
- If the WPILib installer was used, this toolchain is already installed
- Raspberry Pi toolchain (optional)
- Run `./gradlew installArm32Toolchain` after cloning this repository
- SystemCore toolchain (required for SystemCore development)
- Run `./gradlew installSystemCoreToolchain` after cloning this repository
- If the WPILib installer was used, this toolchain is already installed
On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be able to use the macOS x86 roboRIO toolchain on ARM.
On linux, run `sudo apt install gfortran`. This is necessary to be able to build WPIcal on linux platforms.
On linux, run `sudo apt install libx11-dev libgl-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev` to be able to build things depending on glfw.
## Setup
Clone the WPILib repository and follow the instructions above for installing any required tooling. The build process uses versioning information from git. Downloading the source is not sufficient to run the build.

View File

@@ -49,10 +49,10 @@ glfw thirdparty/imgui_suite/glfw
Dear ImGui thirdparty/imgui_suite/imgui
implot thirdparty/imgui_suite/implot
nanopb wpiutil/src/main/native/thirdparty/nanopb
protobuf wpiutil/src/main/native/thirdparty/protobuf
upb wpiutil/src/main/native/thirdparty/upb
mrcal wpical/src/main/native/thirdparty/mrcal
libdogleg wpical/src/main/native/thirdparty/libdogleg
Simd hal/src/main/native/athena/simd
Simd hal/src/main/native/systemcore/simd
Additionally, glfw and nanopb were modified for use in WPILib.
@@ -1355,7 +1355,7 @@ redistribute it freely, subject to the following restrictions:
distribution.
================
protobuf License
upb License
================
Copyright 2008 Google Inc. All rights reserved.

387
WORKSPACE
View File

@@ -1,18 +1,189 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("//thirdparty/ceres:repositories.bzl", "ceres_repositories")
ceres_repositories()
http_archive(
name = "bazel_features",
sha256 = "a015f3f2ebf4f1ac3f4ca8ea371610acb63e1903514fa8725272d381948d2747",
strip_prefix = "bazel_features-1.31.0",
url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.31.0/bazel_features-v1.31.0.tar.gz",
)
# TODO(austin): Upgrade when the patches land.
# https://github.com/bazelbuild/rules_cc/pull/430
# https://github.com/bazelbuild/rules_cc/pull/431
# https://github.com/bazelbuild/rules_cc/pull/432
http_archive(
name = "rules_cc",
patch_args = ["-p1"],
patches = ["//:shared/bazel/patches/rules_cc_windows.patch"],
sha256 = "0d3b4f984c4c2e1acfd1378e0148d35caf2ef1d9eb95b688f8e19ce0c41bdf5b",
strip_prefix = "rules_cc-0.1.4",
url = "https://github.com/bazelbuild/rules_cc/releases/download/0.1.4/rules_cc-0.1.4.tar.gz",
)
# TODO(austinschuh): Update to the next released apple_support once it lands.
# This needs to contain https://github.com/bazelbuild/apple_support/commit/7009b77c98a67d3fea081c9db4dbcee8effc3b7e and should be the next release after 1.22.1
http_archive(
name = "build_bazel_apple_support",
sha256 = "7d542be113180bc1da3660e51fe4792a867fb85537c9ef36a0d3366665a76803",
strip_prefix = "apple_support-7009b77c98a67d3fea081c9db4dbcee8effc3b7e",
url = "https://github.com/bazelbuild/apple_support/archive/7009b77c98a67d3fea081c9db4dbcee8effc3b7e.tar.gz",
)
http_archive(
name = "rules_java",
sha256 = "d31b6c69e479ffa45460b64dc9c7792a431cac721ef8d5219fc9f603fa2ff877",
urls = [
"https://github.com/bazelbuild/rules_java/releases/download/8.11.0/rules_java-8.11.0.tar.gz",
],
)
http_archive(
name = "rules_pkg",
sha256 = "cad05f864a32799f6f9022891de91ac78f30e0fa07dc68abac92a628121b5b11",
urls = [
"https://github.com/bazelbuild/rules_pkg/releases/download/1.0.0/rules_pkg-1.0.0.tar.gz",
],
)
# Rules Python
http_archive(
name = "rules_python",
sha256 = "9f9f3b300a9264e4c77999312ce663be5dee9a56e361a1f6fe7ec60e1beef9a3",
strip_prefix = "rules_python-1.4.1",
url = "https://github.com/bazel-contrib/rules_python/releases/download/1.4.1/rules_python-1.4.1.tar.gz",
)
# Download Extra java rules
http_archive(
name = "rules_jvm_external",
sha256 = "08ea921df02ffe9924123b0686dc04fd0ff875710bfadb7ad42badb931b0fd50",
strip_prefix = "rules_jvm_external-6.1",
url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/6.1/rules_jvm_external-6.1.tar.gz",
sha256 = "4f55980c25d0783b9fe43b049362018d8d79263476b5340a5491893ffcc06ab6",
strip_prefix = "rules_jvm_external-30899314873b6ec69dc7d02c4457fbe52a6e535d",
url = "https://github.com/bazel-contrib/rules_jvm_external/archive/30899314873b6ec69dc7d02c4457fbe52a6e535d.tar.gz",
)
# Setup aspect lib
http_archive(
name = "aspect_bazel_lib",
sha256 = "a8a92645e7298bbf538aa880131c6adb4cf6239bbd27230f077a00414d58e4ce",
strip_prefix = "bazel-lib-2.7.2",
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.7.2/bazel-lib-v2.7.2.tar.gz",
)
# Download toolchains
http_archive(
name = "rules_bzlmodrio_toolchains",
sha256 = "102b4507628e9724b0c1e441727762c344e40170f65ac60516168178ea33a89a",
url = "https://github.com/wpilibsuite/rules_bzlmodrio_toolchains/releases/download/2025-1.bcr6/rules_bzlmodrio_toolchains-2025-1.bcr6.tar.gz",
)
http_archive(
name = "pybind11_bazel",
integrity = "sha256-iwRj1wuX2pDS6t6DqiCfhIXisv4y+7CvxSJtZoSAzGw=",
strip_prefix = "pybind11_bazel-2b6082a4d9d163a52299718113fa41e4b7978db5",
urls = ["https://github.com/pybind/pybind11_bazel/archive/2b6082a4d9d163a52299718113fa41e4b7978db5.tar.gz"],
)
http_archive(
name = "pybind11",
build_file = "@pybind11_bazel//:pybind11-BUILD.bazel",
strip_prefix = "pybind11-dfe7e65b4527eeb11036402aac3a394130960bb2",
urls = ["https://github.com/pybind/pybind11/archive/dfe7e65b4527eeb11036402aac3a394130960bb2.zip"],
)
http_archive(
name = "rules_python_pytest",
sha256 = "e2556404ef56ea3ec938597616afc51d78e1832cfe511b196e9f2b8fd7f8f149",
strip_prefix = "rules_python_pytest-1.1.1",
url = "https://github.com/caseyduquettesc/rules_python_pytest/releases/download/v1.1.1/rules_python_pytest-v1.1.1.tar.gz",
)
http_archive(
name = "bazel_skylib",
sha256 = "51b5105a760b353773f904d2bbc5e664d0987fbaf22265164de65d43e910d8ac",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.8.1/bazel-skylib-1.8.1.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.8.1/bazel-skylib-1.8.1.tar.gz",
],
)
http_archive(
name = "rules_doxygen",
sha256 = "5d154d3d011208510392b5aee8ea23ec61ab858cc1f3382b6eb8c729d3b4b336",
strip_prefix = "rules_doxygen-2.4.2",
url = "https://github.com/TendTo/rules_doxygen/releases/download/2.4.2/rules_doxygen-2.4.2.tar.gz",
)
# This gives us a repository layout which matches what normal BCR modules expect.
# The goal here is to make it easier to depend on external projects which already
# include @eigen without introducing multiple eigen versions.
local_repository(
name = "eigen",
path = "wpimath/src/main/native/thirdparty/eigen/include/",
)
load("@bazel_features//:deps.bzl", "bazel_features_deps")
bazel_features_deps()
load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies")
apple_support_dependencies()
load("@rules_cc//cc:repositories.bzl", "rules_cc_toolchains")
rules_cc_toolchains()
load("@rules_java//java:rules_java_deps.bzl", "rules_java_dependencies")
rules_java_dependencies()
# note that the following line is what is minimally required from protobuf for the java rules
# consider using the protobuf_deps() public API from @com_google_protobuf//:protobuf_deps.bzl
load("@com_google_protobuf//bazel/private:proto_bazel_features.bzl", "proto_bazel_features") # buildifier: disable=bzl-visibility
proto_bazel_features(name = "proto_bazel_features")
# register toolchains
load("@rules_java//java:repositories.bzl", "rules_java_toolchains")
rules_java_toolchains()
load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
py_repositories()
python_register_toolchains(
name = "python_3_10",
ignore_root_user_error = True,
python_version = "3.10",
)
load("@rules_python//python:pip.bzl", "pip_parse")
pip_parse(
name = "allwpilib_pip_deps",
python_interpreter_target = "@python_3_10_host//:python",
requirements_lock = "//:requirements_lock.txt",
requirements_windows = "//:requirements_windows_lock.txt",
)
load("@allwpilib_pip_deps//:requirements.bzl", "install_deps")
install_deps()
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()
load("@rules_jvm_external//:defs.bzl", "maven_install")
load("@rules_jvm_external//:specs.bzl", "maven")
maven_artifacts = [
"org.ejml:ejml-simple:0.44.0",
@@ -21,23 +192,94 @@ maven_artifacts = [
"com.fasterxml.jackson.core:jackson-databind:2.19.2",
"us.hebi.quickbuf:quickbuf-runtime:1.4",
"com.google.code.gson:gson:2.13.1",
"edu.wpi.first.thirdparty.frc2025.opencv:opencv-java:4.10.0-3",
maven.artifact(
"org.junit.jupiter",
"junit-jupiter",
"5.10.1",
testonly = True,
),
maven.artifact(
"org.junit.platform",
"junit-platform-console",
"1.10.1",
testonly = True,
),
maven.artifact(
"org.junit.platform",
"junit-platform-launcher",
"1.10.1",
testonly = True,
),
maven.artifact(
"org.junit.platform",
"junit-platform-reporting",
"1.10.1",
testonly = True,
),
maven.artifact(
"com.google.code.gson",
"gson",
"2.10.1",
testonly = False,
),
maven.artifact(
"org.hamcrest",
"hamcrest-all",
"1.3",
testonly = True,
),
maven.artifact(
"com.googlecode.junit-toolbox",
"junit-toolbox",
"2.4",
testonly = True,
),
maven.artifact(
"org.apache.ant",
"ant",
"1.10.12",
testonly = True,
),
maven.artifact(
"org.apache.ant",
"ant-junit",
"1.10.12",
testonly = True,
),
maven.artifact(
"org.mockito",
"mockito-core",
"4.1.0",
testonly = True,
),
maven.artifact(
"com.google.testing.compile",
"compile-testing",
"0.21.0",
testonly = True,
),
]
maven_install(
name = "maven",
artifacts = maven_artifacts,
maven_install_json = "//:maven_install.json",
repositories = [
"https://repo1.maven.org/maven2",
"https://frcmaven.wpi.edu/artifactory/release/",
],
)
# Download toolchains
http_archive(
name = "rules_bzlmodrio_toolchains",
sha256 = "ff25b5f9445cbd43759be4c6582b987d1065cf817c593eedc7ada1a699298c84",
url = "https://github.com/wpilibsuite/rules_bzlmodRio_toolchains/releases/download/2025-1.bcr2/rules_bzlmodRio_toolchains-2025-1.bcr2.tar.gz",
)
load("@maven//:defs.bzl", "pinned_maven_install")
pinned_maven_install()
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
aspect_bazel_lib_dependencies()
aspect_bazel_lib_register_toolchains()
load("@rules_bzlmodrio_toolchains//:maven_deps.bzl", "setup_legacy_setup_toolchains_dependencies")
@@ -50,8 +292,8 @@ load_toolchains()
#
http_archive(
name = "rules_bzlmodrio_jdk",
sha256 = "81869fe9860e39b17e4a9bc1d33c1ca2faede7e31d9538ed0712406f753a2163",
url = "https://github.com/wpilibsuite/rules_bzlmodRio_jdk/releases/download/17.0.12-7/rules_bzlmodRio_jdk-17.0.12-7.tar.gz",
sha256 = "623b8bcdba1c3140f56e940365f011d2e5d90d74c7a30ace6a8817c037c1dd61",
url = "https://github.com/wpilibsuite/rules_bzlmodRio_jdk/releases/download/17.0.12-7.bcr1/rules_bzlmodrio_jdk-17.0.12-7.bcr1.tar.gz",
)
load("@rules_bzlmodrio_jdk//:maven_deps.bzl", "setup_legacy_setup_jdk_dependencies")
@@ -65,18 +307,9 @@ register_toolchains(
"@local_systemcore//:macos",
"@local_systemcore//:linux",
"@local_systemcore//:windows",
"@local_raspi_bullseye_32//:macos",
"@local_raspi_bullseye_32//:linux",
"@local_raspi_bullseye_32//:windows",
"@local_raspi_bookworm_32//:macos",
"@local_raspi_bookworm_32//:linux",
"@local_raspi_bookworm_32//:windows",
"@local_bullseye_32//:macos",
"@local_bullseye_32//:linux",
"@local_bullseye_32//:windows",
"@local_bullseye_64//:macos",
"@local_bullseye_64//:linux",
"@local_bullseye_64//:windows",
"@local_bookworm_32//:macos",
"@local_bookworm_32//:linux",
"@local_bookworm_32//:windows",
@@ -99,27 +332,113 @@ setup_legacy_bzlmodrio_ni_cpp_dependencies()
http_archive(
name = "bzlmodrio-opencv",
sha256 = "ba3f4910ce9cc0e08abff732aeb5835b1bcfd864ca5296edeadcf2935f7e81b9",
url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2025.4.10.0-3.bcr1/bzlmodRio-opencv-2025.4.10.0-3.bcr1.tar.gz",
sha256 = "867ec3e90b7efc30ff6eb68d14050e7f1e800656d390505b135069f080c5cd91",
url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2025.4.10.0-3.bcr5/bzlmodRio-opencv-2025.4.10.0-3.bcr5.tar.gz",
)
load("@bzlmodrio-opencv//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_opencv_cpp_dependencies")
setup_legacy_bzlmodrio_opencv_cpp_dependencies()
load("@bzlmodrio-opencv//:maven_java_deps.bzl", "setup_legacy_bzlmodrio_opencv_java_dependencies")
setup_legacy_bzlmodrio_opencv_java_dependencies()
http_archive(
name = "build_bazel_apple_support",
sha256 = "c4bb2b7367c484382300aee75be598b92f847896fb31bbd22f3a2346adf66a80",
url = "https://github.com/bazelbuild/apple_support/releases/download/1.15.1/apple_support.1.15.1.tar.gz",
name = "bzlmodrio-libssh",
sha256 = "f8fef627c7b393f7f6ed638e12b80ff90b2cfea11488b15214f25ce1e470723a",
url = "https://github.com/wpilibsuite/bzlmodRio-libssh/releases/download/2024.0.105-1.bcr1/bzlmodrio-libssh-2024.0.105-1.bcr1.tar.gz",
)
load(
"@build_bazel_apple_support//lib:repositories.bzl",
"apple_support_dependencies",
load("@bzlmodrio-libssh//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_libssh_cpp_dependencies")
setup_legacy_bzlmodrio_libssh_cpp_dependencies()
# Setup quickbuf compiler
QUICKBUF_VERSION = "1.3.2"
http_file(
name = "quickbuffer_protoc_linux",
executable = True,
sha256 = "f9a041bccaa7040db523666ef1b5fe9f6f94e70a82c88951f18f58aadd9c50b5",
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-linux-x86_64.exe",
)
apple_support_dependencies()
http_file(
name = "quickbuffer_protoc_osx_x86-64",
executable = True,
sha256 = "ea307c2b69664ae7e7c69db4cddf5803187e5a34bceffd09a21652f0f16044f7",
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-osx-x86_64.exe ",
)
http_file(
name = "quickbuffer_protoc_osx_aarch64",
executable = True,
sha256 = "a9abdee09d8b5ef0aa954b238536917313511deec11e1901994af26ade033e28",
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-osx-aarch_64.exe ",
)
http_file(
name = "quickbuffer_protoc_windows",
executable = True,
sha256 = "27dc1f29764a62b5e6a813a4bcd63e81bbdc3394da760a44acae1025b4a89f1d",
url = "https://repo1.maven.org/maven2/us/hebi/quickbuf/protoc-gen-quickbuf/" + QUICKBUF_VERSION + "/protoc-gen-quickbuf-" + QUICKBUF_VERSION + "-windows-x86_64.exe ",
)
# Setup rules_proto
http_archive(
name = "rules_proto",
sha256 = "0e5c64a2599a6e26c6a03d6162242d231ecc0de219534c38cb4402171def21e8",
strip_prefix = "rules_proto-7.0.2",
url = "https://github.com/bazelbuild/rules_proto/releases/download/7.0.2/rules_proto-7.0.2.tar.gz",
)
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies")
rules_proto_dependencies()
load("@rules_proto//proto:setup.bzl", "rules_proto_setup")
rules_proto_setup()
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
load("@rules_python_pytest//python_pytest:repositories.bzl", "rules_python_pytest_dependencies")
rules_python_pytest_dependencies()
# Capture the repository environmental variables which specify the filter list for what architectures to build in CI.
load("//shared/bazel/rules:publishing_rule.bzl", "publishing_repo")
publishing_repo(
name = "com_wpilib_allwpilib_publishing_config",
)
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()
load("@rules_doxygen//:extensions.bzl", "doxygen_repository")
# Download the os specific version 1.12.0 of doxygen supporting all the indicated platforms
doxygen_repository(
name = "doxygen",
executables = [
"",
"",
"",
],
platforms = [
"windows",
"mac",
"linux",
],
sha256s = [
"07f1c92cbbb32816689c725539c0951f92c6371d3d7f66dfa3192cbe88dd3138",
"6ace7dde967d41f4e293d034a67eb2c7edd61318491ee3131112173a77344001",
"3c42c3f3fb206732b503862d9c9c11978920a8214f223a3950bbf2520be5f647",
],
versions = [
"1.12.0",
"1.12.0",
"1.12.0",
],
)

View File

@@ -1,21 +1,41 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_pkg//:mappings.bzl", "pkg_files")
load("@rules_python//python:defs.bzl", "py_binary")
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library", "wpilib_cc_shared_library", "wpilib_cc_static_library")
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test")
load("//shared/bazel/rules:jni_rules.bzl", "wpilib_jni_cc_library", "wpilib_jni_java_library")
load("//shared/bazel/rules:packaging.bzl", "package_minimal_jni_project")
load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources")
filegroup(
name = "doxygen-files",
srcs = glob([
"src/main/native/include/**/*",
"src/main/native/thirdparty/apriltag/include/**/*",
]),
visibility = ["//visibility:public"],
)
pkg_files(
name = "thirdparty-apriltag-src-pkg",
srcs = glob(["src/main/native/thirdparty/apriltag/include/**"]),
strip_prefix = "src/main/native/thirdparty/apriltag/include",
)
cc_library(
name = "thirdparty-apriltag",
srcs = glob(["src/main/native/thirdparty/apriltag/src/**"]),
hdrs = glob(["src/main/native/thirdparty/apriltag/include/**"]),
copts = select({
"@bazel_tools//src/conditions:darwin": [
"@platforms//os:osx": [
"-Wno-format-nonliteral",
"-Wno-gnu-zero-variadic-macro-arguments",
"-Wno-uninitialized",
"-Wno-sign-compare",
"-Wno-type-limits",
],
"@bazel_tools//src/conditions:windows": [
"@platforms//os:windows": [
"/wd4005",
"/wd4018",
"/wd4244",
@@ -31,7 +51,6 @@ cc_library(
}),
includes = ["src/main/native/thirdparty/apriltag/include/common"],
strip_include_prefix = "src/main/native/thirdparty/apriltag/include",
visibility = ["//visibility:public"],
)
generate_resources(
@@ -39,29 +58,83 @@ generate_resources(
namespace = "frc",
prefix = "APRILTAG",
resource_files = glob(["src/main/native/resources/**"]),
visibility = ["//visibility:public"],
)
cc_library(
name = "apriltag.static",
wpilib_cc_library(
name = "apriltag",
srcs = [":generate-resources"] + glob(
["src/main/native/cpp/**"],
exclude = ["src/main/native/cpp/jni/**"],
),
hdrs = glob(["src/main/native/include/**/*"]),
defines = ["WPILIB_EXPORTS"],
extra_hdr_pkg_files = [":thirdparty-apriltag-src-pkg"],
extra_src_pkg_files = [":apriltag-java-jni-hdrs-pkg"],
local_defines = ["WPILIB_EXPORTS"],
strip_include_prefix = "src/main/native/include",
visibility = ["//visibility:public"],
deps = [
":thirdparty-apriltag",
"//wpimath:wpimath.static",
"//wpiutil:wpiutil.static",
"//wpimath",
"//wpiutil",
],
)
java_library(
wpilib_cc_shared_library(
name = "shared/apriltag",
auto_export_windows_symbols = False,
dynamic_deps = [
"//wpimath:shared/wpimath",
"//wpiutil:shared/wpiutil",
],
visibility = ["//visibility:public"],
deps = [
":apriltag",
":thirdparty-apriltag",
],
)
wpilib_cc_static_library(
name = "static/apriltag",
static_deps = [
"//wpimath:static/wpimath",
"//wpiutil:static/wpiutil",
],
visibility = ["//visibility:public"],
deps = [
":apriltag",
],
)
wpilib_jni_cc_library(
name = "apriltagjni",
srcs = glob(["src/main/native/cpp/jni/**"]),
java_dep = ":apriltag-java",
visibility = ["//visibility:public"],
deps = [
":apriltag",
],
)
wpilib_cc_shared_library(
name = "shared/apriltagjni",
auto_export_windows_symbols = False,
dynamic_deps = [
":shared/apriltag",
"//wpimath:shared/wpimath",
"//wpiutil:shared/wpiutil",
],
use_debug_name = False,
visibility = ["//visibility:public"],
deps = [":apriltagjni"],
)
wpilib_jni_java_library(
name = "apriltag-java",
srcs = glob(["src/main/java/**/*.java"]),
extra_source_pkgs = ["resources"],
maven_artifact_name = "apriltag-java",
maven_group_id = "edu.wpi.first.apriltag",
native_libs = [":apriltagjni"],
resource_strip_prefix = "apriltag/src/main/native/resources",
resources = glob(["src/main/native/resources/**"]),
visibility = ["//visibility:public"],
@@ -75,6 +148,12 @@ java_library(
],
)
pkg_files(
name = "resources",
srcs = glob(["src/main/native/resources/**"]),
strip_prefix = "src/main/native/resources/",
)
cc_test(
name = "apriltag-cpp-test",
size = "small",
@@ -83,8 +162,22 @@ cc_test(
"no-asan",
],
deps = [
":apriltag.static",
"//thirdparty/googletest:googletest.static",
":apriltag",
"//thirdparty/googletest",
],
)
wpilib_java_junit5_test(
name = "apriltag-java-test",
srcs = glob(["src/test/java/**/*.java"]),
resource_strip_prefix = "apriltag/src/test/resources",
resources = glob(["src/test/resources/**"]),
deps = [
":apriltag-java",
"//wpimath:wpimath-java",
"//wpiutil:wpiutil-java",
"@bzlmodrio-opencv//libraries/java/opencv",
"@maven//:com_fasterxml_jackson_core_jackson_databind",
],
)
@@ -92,7 +185,7 @@ cc_binary(
name = "DevMain-Cpp",
srcs = ["src/dev/native/cpp/main.cpp"],
deps = [
":apriltag.static",
":apriltag",
],
)
@@ -108,5 +201,14 @@ java_binary(
py_binary(
name = "convert_apriltag_layouts",
srcs = ["convert_apriltag_layouts.py"],
tags = ["manual"],
target_compatible_with = select({
"@rules_bzlmodrio_toolchains//constraints/is_systemcore:systemcore": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
)
package_minimal_jni_project(
name = "apriltag",
maven_artifact_name = "apriltag-cpp",
maven_group_id = "edu.wpi.first.apriltag",
)

View File

@@ -0,0 +1,4 @@
robotpy-apriltag
================
RobotPy wrappers around WPILib's version of the apriltag library.

View File

@@ -0,0 +1,41 @@
[build-system]
build-backend = "hatchling.build"
requires = [
"hatchling",
"hatch-nativelib~=0.2.0",
"hatch-robotpy~=0.2.1",
"robotpy-native-wpiutil==2027.0.0a2",
"robotpy-native-wpimath==2027.0.0a2",
]
[project]
name = "robotpy-native-apriltag"
version = "2027.0.0a2"
description = "WPILib AprilTag Library"
license = "BSD-3-Clause"
dependencies = [
"robotpy-native-wpiutil==2027.0.0a2",
"robotpy-native-wpimath==2027.0.0a2",
]
[tool.hatch.build.targets.wheel]
packages = ["src/native"]
[[tool.hatch.build.hooks.robotpy.maven_lib_download]]
artifact_id = "apriltag-cpp"
group_id = "edu.wpi.first.apriltag"
repo_url = "https://frcmaven.wpi.edu/artifactory/release-2027"
version = "2027.0.0-alpha-2"
extract_to = "src/native/apriltag"
libs = ["apriltag"]
[[tool.hatch.build.hooks.nativelib.pcfile]]
pcfile = "src/native/apriltag/robotpy-native-apriltag.pc"
name = "apriltag"
includedir = "src/native/apriltag/include"
libdir = "src/native/apriltag/lib"
shared_libraries = ["apriltag"]
requires = ["robotpy-native-wpiutil", "robotpy-native-wpimath"]

View File

@@ -0,0 +1,74 @@
[build-system]
build-backend = "hatchling.build"
requires = [
"semiwrap~=0.1.7",
"hatch-meson~=0.1.0b2",
"hatch-robotpy~=0.2.1",
"hatchling",
"robotpy-native-apriltag==2027.0.0a2",
"robotpy-wpiutil==2027.0.0a2",
"robotpy-wpimath==2027.0.0a2",
]
[project]
name = "robotpy-apriltag"
version = "2027.0.0a2"
description = "RobotPy bindings for WPILib's AprilTag library"
authors = [
{name = "RobotPy Development Team", email = "robotpy@googlegroups.com"},
]
license = "BSD-3-Clause"
dependencies = [
"robotpy-native-apriltag==2027.0.0a2",
"robotpy-wpiutil==2027.0.0a2",
"robotpy-wpimath==2027.0.0a2",
]
[project.urls]
"Source code" = "https://github.com/robotpy/mostrobotpy"
[tool.hatch.build.hooks.robotpy]
version_file = "robotpy_apriltag/version.py"
[tool.hatch.build.hooks.semiwrap]
[tool.hatch.build.hooks.meson]
[tool.hatch.build.targets.wheel]
packages = ["robotpy_apriltag"]
[tool.semiwrap]
update_init = [
"robotpy_apriltag robotpy_apriltag._apriltag"
]
scan_headers_ignore = [
"common/*",
"test/*",
"apriltag.h",
"apriltag_math.h",
"apriltag_pose.h",
"frc/apriltag/AprilTagDetector_cv.h",
"tag16h5.h",
"tag36h11.h",
]
[tool.semiwrap.extension_modules."robotpy_apriltag._apriltag"]
name = "apriltag"
wraps = ["robotpy-native-apriltag"]
depends = ["wpiutil", "wpimath"]
[tool.semiwrap.extension_modules."robotpy_apriltag._apriltag".headers]
# frc/apriltag
AprilTag = "frc/apriltag/AprilTag.h"
AprilTagDetection = "frc/apriltag/AprilTagDetection.h"
AprilTagDetector = "frc/apriltag/AprilTagDetector.h"
# AprilTagDetector_cv = "frc/apriltag/AprilTagDetector_cv.h"
AprilTagFieldLayout = "frc/apriltag/AprilTagFieldLayout.h"
AprilTagFields = "frc/apriltag/AprilTagFields.h"
AprilTagPoseEstimate = "frc/apriltag/AprilTagPoseEstimate.h"
AprilTagPoseEstimator = "frc/apriltag/AprilTagPoseEstimator.h"

View File

@@ -0,0 +1,22 @@
from . import _init__apriltag
# autogenerated by 'semiwrap create-imports robotpy_apriltag robotpy_apriltag._apriltag'
from ._apriltag import (
AprilTag,
AprilTagDetection,
AprilTagDetector,
AprilTagField,
AprilTagFieldLayout,
AprilTagPoseEstimate,
AprilTagPoseEstimator,
)
__all__ = [
"AprilTag",
"AprilTagDetection",
"AprilTagDetector",
"AprilTagField",
"AprilTagFieldLayout",
"AprilTagPoseEstimate",
"AprilTagPoseEstimator",
]

View File

@@ -0,0 +1,4 @@
#include "semiwrap_init.robotpy_apriltag._apriltag.hpp"
SEMIWRAP_PYBIND11_MODULE(m) { initWrapper(m); }

View File

@@ -0,0 +1,16 @@
functions:
to_json:
ignore: true
from_json:
ignore: true
classes:
frc::AprilTag:
attributes:
ID:
pose:
methods:
Generate36h11AprilTagImage:
ignore: true
Generate16h5AprilTagImage:
ignore: true
operator==:

View File

@@ -0,0 +1,43 @@
extra_includes:
- pybind11/eigen.h
classes:
frc::AprilTagDetection:
methods:
GetFamily:
GetId:
GetHamming:
GetDecisionMargin:
GetHomography:
GetHomographyMatrix:
GetCenter:
GetCorner:
GetCorners:
inline_code: |
.def("__repr__", [](const AprilTagDetection &self) {
return py::str("<AprilTagDetection tag_family={} tag_id={} hamming={} decision_margin={} center={}>")
.format(self.GetFamily(), self.GetId(), self.GetHamming(), self.GetDecisionMargin(), self.GetCenter());
})
frc::AprilTagDetection::Point:
attributes:
x:
y:
inline_code: |
.def(py::init([](double x, double y) {
AprilTagDetection::Point pt{x, y};
return std::make_unique<AprilTagDetection::Point>(std::move(pt));
}), py::arg("x"), py::arg("y"))
.def("__len__", [](const AprilTagDetection::Point &self) { return 2; })
.def("__getitem__", [](const AprilTagDetection::Point &self, int index) {
switch (index) {
case 0:
return self.x;
case 1:
return self.y;
default:
throw std::out_of_range("AprilTagDetection.Point index out of range");
}
})
.def("__repr__", [](const AprilTagDetection::Point &self) {
return py::str("AprilTagDetection.Point(x={}, y={})").format(self.x, self.y);
})

View File

@@ -0,0 +1,99 @@
extra_includes:
- pybind11_typing.h
classes:
frc::AprilTagDetector:
methods:
AprilTagDetector:
SetConfig:
GetConfig:
SetQuadThresholdParameters:
GetQuadThresholdParameters:
AddFamily:
RemoveFamily:
ClearFamilies:
Detect:
overloads:
int, int, int, uint8_t*:
ignore: true
int, int, uint8_t*:
ignore: true
inline_code: |
.def("detect", [](AprilTagDetector *self, py::buffer img) {
// validate the input image buffer
auto buf = img.request();
if (buf.ndim != 2) {
throw py::value_error("buffer must only have two dimensions");
} else if (buf.itemsize != 1) {
throw py::value_error("buffer elements must be bytes");
}
// We are going to move the detection result into this shared_ptr
// so that python can keep it alive. We don't expose the result directly
// to the user because we'd have to pretend it's a list, and that would
// be annoying.
std::shared_ptr<AprilTagDetector::Results> c_result;
{
py::gil_scoped_release unlock;
c_result = std::make_shared<AprilTagDetector::Results>(std::move(self->Detect(buf.shape[1], buf.shape[0], (uint8_t*)buf.ptr)));
}
// This tells python about the shared_ptr, and it'll keep it alive as
// long as the python reference is alive. When we call get(), we marked
// the return value as reference_internal so python will keep the python
// reference for the results object alive for as long as all of its
// results that we put into the list are alive
py::object py_result = py::cast(c_result);
auto len = c_result->size();
auto get = py_result.attr("get");
py::typing::List<AprilTagDetection> l(len);
for (size_t i = 0; i < len; i++) {
l[i] = get(i);
}
return l;
}, py::arg("image"),
R"doc(
Detect tags from an 8-bit grayscale image with shape (height, width)
:return: list of results
)doc"
)
frc::AprilTagDetector::Config:
attributes:
numThreads:
quadDecimate:
quadSigma:
refineEdges:
decodeSharpening:
debug:
methods:
operator==:
frc::AprilTagDetector::QuadThresholdParameters:
attributes:
minClusterPixels:
maxNumMaxima:
criticalAngle:
maxLineFitMSE:
minWhiteBlackDiff:
deglitch:
methods:
operator==:
frc::AprilTagDetector::Results:
rename: _Results
ignored_bases:
- std::span<const AprilTagDetection* const>
force_no_trampoline: true
methods:
Results:
overloads:
'':
ignore: true
void*, const private_init&:
ignore: true
inline_code: |
// use the keepalive to keep the array of results around until
// the user deletes them
.def("get", [](const AprilTagDetector::Results &self, int i) {
return self[i];
}, py::return_value_policy::reference_internal)

View File

@@ -0,0 +1,29 @@
functions:
to_json:
ignore: true
from_json:
ignore: true
LoadAprilTagLayoutField:
ignore: true
classes:
frc::AprilTagFieldLayout:
enums:
OriginPosition:
methods:
AprilTagFieldLayout:
overloads:
'':
std::string_view:
std::vector<AprilTag>, units::meter_t, units::meter_t:
LoadField:
GetFieldLength:
GetFieldWidth:
GetTags:
SetOrigin:
overloads:
OriginPosition:
const Pose3d&:
GetOrigin:
GetTagPose:
Serialize:
operator==:

View File

@@ -0,0 +1,2 @@
enums:
AprilTagField:

View File

@@ -0,0 +1,9 @@
classes:
frc::AprilTagPoseEstimate:
attributes:
pose1:
pose2:
error1:
error2:
methods:
GetAmbiguity:

View File

@@ -0,0 +1,36 @@
extra_includes:
- frc/apriltag/AprilTagDetection.h
classes:
frc::AprilTagPoseEstimator:
methods:
AprilTagPoseEstimator:
SetConfig:
GetConfig:
EstimateHomography:
overloads:
const AprilTagDetection& [const]:
std::span<const double, 9> [const]:
EstimateOrthogonalIteration:
overloads:
const AprilTagDetection&, int [const]:
std::span<const double, 9>, std::span<const double, 8>, int [const]:
Estimate:
overloads:
const AprilTagDetection& [const]:
std::span<const double, 9>, std::span<const double, 8> [const]:
frc::AprilTagPoseEstimator::Config:
force_no_default_constructor: true
attributes:
tagSize:
fx:
fy:
cx:
cy:
methods:
operator==:
inline_code: |
.def(py::init([](units::meter_t tagSize, double fx, double fy, double cx, double cy) {
AprilTagPoseEstimator::Config cfg{tagSize, fx, fy, cx, cy};
return std::make_unique<AprilTagPoseEstimator::Config>(std::move(cfg));
}), py::arg("tagSize"), py::arg("fx"), py::arg("fy"), py::arg("cx"), py::arg("cy"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,119 @@
import cv2
import robotpy_apriltag
from wpimath.geometry import Transform3d
import math
import pathlib
import pytest
def test_point():
point = robotpy_apriltag.AprilTagDetection.Point()
x, y = point
assert x == 0
assert y == 0
def _load_grayscale_image(fname):
full_path = pathlib.Path(__file__).parent / fname
img = cv2.imread(str(full_path))
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
def test_1():
detector = robotpy_apriltag.AprilTagDetector()
assert detector.addFamily("tag16h5")
assert detector.addFamily("tag36h11")
img = _load_grayscale_image("tag1_640_480.jpg")
results = detector.detect(img)
assert len(results) == 1
assert results[0].getFamily() == "tag36h11"
assert results[0].getId() == 1
assert results[0].getHamming() == 0
estimator = robotpy_apriltag.AprilTagPoseEstimator(
robotpy_apriltag.AprilTagPoseEstimator.Config(0.2, 500, 500, 320, 240)
)
est = estimator.estimateOrthogonalIteration(results[0], 50)
assert est.pose2 == Transform3d()
pose = estimator.estimate(results[0])
assert est.pose1 == pose
def test_pose_rotated_x():
"""
This tag is rotated such that the top is closer to the camera than the bottom. In the camera
frame, with +x to the right, this is a rotation about +X by 45 degrees.
"""
detector = robotpy_apriltag.AprilTagDetector()
assert detector.addFamily("tag16h5")
img = _load_grayscale_image("tag2_45deg_X.png")
results = detector.detect(img)
assert len(results) == 1
estimator = robotpy_apriltag.AprilTagPoseEstimator(
robotpy_apriltag.AprilTagPoseEstimator.Config(
0.2, 500, 500, img.shape[1] / 2.0, img.shape[0] / 2.0
)
)
est = estimator.estimateOrthogonalIteration(results[0], 50)
assert pytest.approx(est.pose1.rotation().x, abs=0.1) == math.radians(45)
assert pytest.approx(est.pose1.rotation().y, abs=0.1) == math.radians(0)
assert pytest.approx(est.pose1.rotation().z, abs=0.1) == math.radians(0)
def test_pose_rotated_y():
"""
This tag is rotated such that the right is closer to the camera than the left. In the camera
frame, with +y down, this is a rotation of 45 degrees about +y.
"""
detector = robotpy_apriltag.AprilTagDetector()
assert detector.addFamily("tag16h5")
img = _load_grayscale_image("tag2_45deg_y.png")
results = detector.detect(img)
assert len(results) == 1
estimator = robotpy_apriltag.AprilTagPoseEstimator(
robotpy_apriltag.AprilTagPoseEstimator.Config(
0.2, 500, 500, img.shape[1] / 2.0, img.shape[0] / 2.0
)
)
est = estimator.estimateOrthogonalIteration(results[0], 50)
assert pytest.approx(est.pose1.rotation().x, abs=0.1) == math.radians(0)
assert pytest.approx(est.pose1.rotation().y, abs=0.1) == math.radians(45)
assert pytest.approx(est.pose1.rotation().z, abs=0.1) == math.radians(0)
def test_pose_straight_on():
"""
This tag is facing right at the camera -- no rotation should be observed.
"""
detector = robotpy_apriltag.AprilTagDetector()
assert detector.addFamily("tag16h5")
img = _load_grayscale_image("tag2_16h5_straight.png")
results = detector.detect(img)
assert len(results) == 1
estimator = robotpy_apriltag.AprilTagPoseEstimator(
robotpy_apriltag.AprilTagPoseEstimator.Config(
0.2, 500, 500, img.shape[1] / 2.0, img.shape[0] / 2.0
)
)
est = estimator.estimateOrthogonalIteration(results[0], 50)
assert pytest.approx(est.pose1.rotation().x, abs=0.1) == math.radians(0)
assert pytest.approx(est.pose1.rotation().y, abs=0.1) == math.radians(0)
assert pytest.approx(est.pose1.rotation().z, abs=0.1) == math.radians(0)

View File

@@ -1,92 +0,0 @@
# Testing steps for real hardware
trigger:
batch: true
branches:
include:
- master
stages:
- stage: Build
jobs:
- job: IntegrationTests
displayName: Integration Tests
pool:
vmImage: 'ubuntu-latest'
container:
image: wpilib/roborio-cross-ubuntu:2023-22.04
timeoutInMinutes: 0
steps:
- task: Gradle@2
condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')))
inputs:
workingDirectory: ""
gradleWrapperFile: "gradlew"
gradleOptions: "-Xmx3072m"
publishJUnitResults: false
testResultsFiles: "**/TEST-*.xml"
tasks: "copyWpilibJIntegrationTestJarToOutput copyWpilibCTestLibrariesToOutput"
options: "-Ponlylinuxathena -PbuildServer -PskipJavaFormat"
- task: PublishPipelineArtifact@0
inputs:
artifactName: "Integration Tests"
targetPath: "build/integrationTestFiles"
- stage: TestBench
displayName: Test Bench
condition: false
jobs:
- job: Cpp
displayName: C++
pool: RoboRioConnections
timeoutInMinutes: 30
workspace:
clean: all
steps:
- task: DownloadPipelineArtifact@0
inputs:
artifactName: "Integration Tests"
targetPath: "build/integrationTestFiles"
- task: ShellScript@2
displayName: Run C++ Tests
inputs:
scriptPath: test-scripts/deploy-and-run-test-on-robot.sh
args: 'cpp -A "--gtest_output=xml:/home/admin/testResults/cppreport.xml"'
- task: PublishTestResults@2
displayName: Publish C++ Test Results
inputs:
testResultsFormat: "JUnit"
testResultsFiles: "*.xml"
testRunTitle: "C++ Test Report"
searchFolder: "$(System.DefaultWorkingDirectory)/test-reports"
- job: Java
pool: RoboRioConnections
timeoutInMinutes: 30
workspace:
clean: all
steps:
- task: DownloadPipelineArtifact@0
inputs:
artifactName: "Integration Tests"
targetPath: "build/integrationTestFiles"
- task: ShellScript@2
displayName: Run Java Tests
inputs:
scriptPath: test-scripts/deploy-and-run-test-on-robot.sh
args: "java"
- task: PublishTestResults@2
displayName: Publish Java Test Results
inputs:
testResultsFormat: "JUnit"
testResultsFiles: "*.xml"
testRunTitle: "Java Test Report"
searchFolder: "$(System.DefaultWorkingDirectory)/test-reports"

View File

@@ -39,7 +39,7 @@ public class Main {
private static final TravelingSalesman twistTraveler =
new TravelingSalesman(
(pose1, pose2) -> {
var twist = pose1.log(pose2);
var twist = pose2.minus(pose1).log();
return Math.hypot(twist.dx, twist.dy);
});

View File

@@ -31,7 +31,7 @@ BENCHMARK(BM_Transform);
void BM_Twist(benchmark::State& state) {
frc::TravelingSalesman traveler{[](auto pose1, auto pose2) {
auto twist = pose1.Log(pose2);
auto twist = (pose2 - pose1).Log();
return units::math::hypot(twist.dx, twist.dy).value();
}};
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)

View File

@@ -11,7 +11,7 @@ buildscript {
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.1'
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2025.0'
id 'edu.wpi.first.NativeUtils' apply false
id 'edu.wpi.first.GradleJni' version '1.2.0'
id 'edu.wpi.first.GradleVsCode'
@@ -32,6 +32,7 @@ allprojects {
url = 'https://frcmaven.wpi.edu/artifactory/ex-mvn'
}
}
wpilibRepositories.use2027Repos()
if (project.hasProperty('releaseMode')) {
wpilibRepositories.addAllReleaseRepositories(it)
} else {
@@ -110,8 +111,8 @@ subprojects {
plugins.withType(JavaPlugin) {
java {
sourceCompatibility = 17
targetCompatibility = 17
sourceCompatibility = 21
targetCompatibility = 21
}
}

View File

@@ -1,8 +1,20 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("@bzlmodrio-opencv//libraries/cpp/opencv:libraries.bzl", "opencv_shared_libraries")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_test")
load("@rules_java//java:defs.bzl", "java_binary")
load("//shared/bazel/rules:cc_rules.bzl", "wpilib_cc_library", "wpilib_cc_shared_library", "wpilib_cc_static_library")
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_library")
load("//shared/bazel/rules:packaging.bzl", "package_minimal_cc_project")
cc_library(
name = "cameraserver.static",
filegroup(
name = "doxygen-files",
srcs = glob([
"src/main/native/include/**/*",
]),
visibility = ["//visibility:public"],
)
wpilib_cc_library(
name = "cameraserver",
srcs = glob(["src/main/native/cpp/**"]),
hdrs = glob(["src/main/native/include/**/*"]),
includes = [
@@ -12,19 +24,42 @@ cc_library(
strip_include_prefix = "src/main/native/include",
visibility = ["//visibility:public"],
deps = [
"//cscore:cscore.static",
"//ntcore:ntcore.static",
"//cscore",
"//ntcore",
],
)
java_library(
wpilib_cc_shared_library(
name = "shared/cameraserver",
dynamic_deps = [
"//cscore:shared/cscore",
"//ntcore:shared/ntcore",
"//wpiutil:shared/wpiutil",
] + opencv_shared_libraries,
visibility = ["//visibility:public"],
deps = [":cameraserver"],
)
wpilib_cc_static_library(
name = "static/cameraserver",
static_deps = [
"//cscore:static/cscore",
"//ntcore:static/ntcore",
],
visibility = ["//visibility:public"],
deps = [":cameraserver"],
)
wpilib_java_library(
name = "cameraserver-java",
srcs = glob(["src/main/java/**/*.java"]),
maven_artifact_name = "cameraserver-java",
maven_group_id = "edu.wpi.first.cameraserver",
visibility = ["//visibility:public"],
deps = [
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:networktables-java",
"//ntcore:ntcore-java",
"//wpimath:wpimath-java",
"//wpinet:wpinet-java",
"//wpiutil:wpiutil-java",
@@ -37,8 +72,8 @@ cc_test(
size = "small",
srcs = glob(["src/test/native/**"]),
deps = [
":cameraserver.static",
"//thirdparty/googletest:googletest.static",
":cameraserver",
"//thirdparty/googletest",
],
)
@@ -46,7 +81,7 @@ cc_binary(
name = "DevMain-Cpp",
srcs = ["src/dev/native/cpp/main.cpp"],
deps = [
":cameraserver.static",
":cameraserver",
],
)
@@ -57,3 +92,9 @@ java_binary(
deps = [
],
)
package_minimal_cc_project(
name = "cameraserver",
maven_artifact_name = "cameraserver-cpp",
maven_group_id = "edu.wpi.first.cameraserver",
)

View File

@@ -1,6 +1,7 @@
include(CMakeFindDependencyMacro)
@FILENAME_DEP_REPLACE@
@WPIUTIL_DEP_REPLACE@
@DATALOG_DEP_REPLACE@
@NTCORE_DEP_REPLACE@
@CSCORE_DEP_REPLACE@
find_dependency(OpenCV)

View File

@@ -8,7 +8,7 @@ java_binary(
"//cameraserver:cameraserver-java",
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:networktables-java",
"//ntcore:ntcore-java",
"//wpimath:wpimath-java",
"//wpiutil:wpiutil-java",
"@maven//:com_google_code_gson_gson",

View File

@@ -61,9 +61,6 @@ model {
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
nativeUtils.useRequiredLibrary(binary, 'ni_link_libraries', 'ni_runtime_libraries')
}
}
}
}

View File

@@ -176,7 +176,7 @@ public final class Main {
} else {
System.out.println("Setting up NetworkTables client for team " + team);
ntinst.setServerTeam(team);
ntinst.startClient4("multicameraserver");
ntinst.startClient("multicameraserver");
}
// start cameras

View File

@@ -190,7 +190,7 @@ int main(int argc, char* argv[]) {
ntinst.StartServer();
} else {
wpi::print("Setting up NetworkTables client for team {}\n", team);
ntinst.StartClient4("multicameraserver");
ntinst.StartClient("multicameraserver");
ntinst.SetServerTeam(team);
}

View File

@@ -4,7 +4,6 @@
package edu.wpi.first.cameraserver;
import edu.wpi.first.cscore.AxisCamera;
import edu.wpi.first.cscore.CameraServerJNI;
import edu.wpi.first.cscore.CvSink;
import edu.wpi.first.cscore.CvSource;
@@ -541,9 +540,7 @@ public final class CameraServer {
* @return The USB camera capturing images.
*/
public static UsbCamera startAutomaticCapture() {
UsbCamera camera = startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
return camera;
return startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
}
/**
@@ -558,7 +555,7 @@ public final class CameraServer {
public static UsbCamera startAutomaticCapture(int dev) {
UsbCamera camera = new UsbCamera("USB Camera " + dev, dev);
startAutomaticCapture(camera);
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "auto");
return camera;
}
@@ -572,7 +569,7 @@ public final class CameraServer {
public static UsbCamera startAutomaticCapture(String name, int dev) {
UsbCamera camera = new UsbCamera(name, dev);
startAutomaticCapture(camera);
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "name");
return camera;
}
@@ -586,7 +583,7 @@ public final class CameraServer {
public static UsbCamera startAutomaticCapture(String name, String path) {
UsbCamera camera = new UsbCamera(name, path);
startAutomaticCapture(camera);
CameraServerSharedStore.getCameraServerShared().reportUsbCamera(camera.getHandle());
CameraServerSharedStore.reportUsage("UsbCamera[" + path + "]", "path");
return camera;
}
@@ -603,72 +600,6 @@ public final class CameraServer {
return server;
}
/**
* Adds an Axis IP camera.
*
* <p>This overload calls {@link #addAxisCamera(String, String)} with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String host) {
return addAxisCamera("Axis Camera", host);
}
/**
* Adds an Axis IP camera.
*
* <p>This overload calls {@link #addAxisCamera(String, String[])} with name "Axis Camera".
*
* @param hosts Array of Camera host IPs/DNS names
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String[] hosts) {
return addAxisCamera("Axis Camera", hosts);
}
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String name, String host) {
AxisCamera camera = new AxisCamera(name, host);
// Create a passthrough MJPEG server for USB access
startAutomaticCapture(camera);
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
return camera;
}
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String name, String[] hosts) {
AxisCamera camera = new AxisCamera(name, hosts);
// Create a passthrough MJPEG server for USB access
startAutomaticCapture(camera);
CameraServerSharedStore.getCameraServerShared().reportAxisCamera(camera.getHandle());
return camera;
}
/**
* Adds a virtual camera for switching between two streams. Unlike the other addCamera methods,
* this returns a VideoSink rather than a VideoSource. Calling setSource() on the returned object

View File

@@ -21,25 +21,12 @@ public interface CameraServerShared {
void reportDriverStationError(String error);
/**
* Report an video server usage.
* Report usage.
*
* @param id the usage id
* @param resource the resource name
* @param data arbitrary string data
*/
void reportVideoServer(int id);
/**
* Report a usb camera usage.
*
* @param id the usage id
*/
void reportUsbCamera(int id);
/**
* Report an axis camera usage.
*
* @param id the usage id
*/
void reportAxisCamera(int id);
void reportUsage(String resource, String data);
/**
* Get if running on a roboRIO.

View File

@@ -20,17 +20,11 @@ public final class CameraServerSharedStore {
cameraServerShared =
new CameraServerShared() {
@Override
public void reportVideoServer(int id) {}
@Override
public void reportUsbCamera(int id) {}
public void reportUsage(String resource, String data) {}
@Override
public void reportDriverStationError(String error) {}
@Override
public void reportAxisCamera(int id) {}
@Override
public Long getRobotMainThreadId() {
return null;
@@ -40,6 +34,16 @@ public final class CameraServerSharedStore {
return cameraServerShared;
}
/**
* Report usage.
*
* @param resource the resource name
* @param data arbitrary string data
*/
public static void reportUsage(String resource, String data) {
getCameraServerShared().reportUsage(resource, data);
}
/**
* Set the CameraServerShared object.
*

View File

@@ -73,7 +73,7 @@ public class VisionRunner<P extends VisionPipeline> {
public void runOnce() {
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
if (id != null && Thread.currentThread().getId() == id) {
if (id != null && Thread.currentThread().threadId() == id) {
throw new IllegalStateException(
"VisionRunner.runOnce() cannot be called from the main robot thread");
}
@@ -106,7 +106,7 @@ public class VisionRunner<P extends VisionPipeline> {
public void runForever() {
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
if (id != null && Thread.currentThread().getId() == id) {
if (id != null && Thread.currentThread().threadId() == id) {
throw new IllegalStateException(
"VisionRunner.runForever() cannot be called from the main robot thread");
}

View File

@@ -184,9 +184,9 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
value = "mjpg:" + value;
}
#ifdef __FRC_ROBORIO__
#ifdef __FRC_SYSTEMCORE__
// Look to see if we have a passthrough server for this source
// Only do this on the roboRIO
// Only do this on the systemcore
for (const auto& i : m_sinks) {
CS_Sink sink = i.second.GetHandle();
CS_Source sinkSource = cs::GetSinkSource(sink, &status);
@@ -471,11 +471,7 @@ Instance::Instance() {
}
cs::UsbCamera CameraServer::StartAutomaticCapture() {
cs::UsbCamera camera =
StartAutomaticCapture(::GetInstance().m_defaultUsbDevice++);
auto csShared = GetCameraServerShared();
csShared->ReportUsbCamera(camera.GetHandle());
return camera;
return StartAutomaticCapture(::GetInstance().m_defaultUsbDevice++);
}
cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
@@ -483,7 +479,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
cs::UsbCamera camera{fmt::format("USB Camera {}", dev), dev};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportUsbCamera(camera.GetHandle());
csShared->ReportUsage(fmt::format("UsbCamera[{}]", dev), "auto");
return camera;
}
@@ -493,7 +489,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
cs::UsbCamera camera{name, dev};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportUsbCamera(camera.GetHandle());
csShared->ReportUsage(fmt::format("UsbCamera[{}]", dev), "name");
return camera;
}
@@ -503,67 +499,10 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
cs::UsbCamera camera{name, path};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportUsbCamera(camera.GetHandle());
csShared->ReportUsage(fmt::format("UsbCamera[{}]", path), "path");
return camera;
}
WPI_IGNORE_DEPRECATED
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view host) {
return AddAxisCamera("Axis Camera", host);
}
cs::AxisCamera CameraServer::AddAxisCamera(const char* host) {
return AddAxisCamera("Axis Camera", host);
}
cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
return AddAxisCamera("Axis Camera", host);
}
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
return AddAxisCamera("Axis Camera", hosts);
}
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
std::string_view host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportAxisCamera(camera.GetHandle());
return camera;
}
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
const char* host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportAxisCamera(camera.GetHandle());
return camera;
}
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
const std::string& host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportAxisCamera(camera.GetHandle());
return camera;
}
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
std::span<const std::string> hosts) {
::GetInstance();
cs::AxisCamera camera{name, hosts};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
csShared->ReportAxisCamera(camera.GetHandle());
return camera;
}
WPI_UNIGNORE_DEPRECATED
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
auto& inst = ::GetInstance();
// create a dummy CvSource

View File

@@ -12,9 +12,7 @@
namespace {
class DefaultCameraServerShared : public frc::CameraServerShared {
public:
void ReportUsbCamera(int id) override {}
void ReportAxisCamera(int id) override {}
void ReportVideoServer(int id) override {}
void ReportUsage(std::string_view resource, std::string_view data) override {}
void SetCameraServerErrorV(fmt::string_view format,
fmt::format_args args) override {}
void SetVisionRunnerErrorV(fmt::string_view format,

View File

@@ -11,8 +11,6 @@
#include <string_view>
#include <vector>
#include <wpi/deprecated.h>
#include "cscore_cv.h"
namespace frc {
@@ -75,128 +73,6 @@ class CameraServer {
*/
static cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
WPI_IGNORE_DEPRECATED
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view host);
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(const char* host);
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(const std::string& host);
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts) {
return AddAxisCamera("Axis Camera", hosts);
}
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::string_view host);
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name, const char* host);
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
const std::string& host);
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::span<const std::string> hosts);
/**
* Adds an Axis IP camera.
*
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::initializer_list<T> hosts) {
std::vector<std::string> vec;
vec.reserve(hosts.size());
for (const auto& host : hosts) {
vec.emplace_back(host);
}
return AddAxisCamera(name, vec);
}
WPI_UNIGNORE_DEPRECATED
/**
* Adds a virtual camera for switching between two streams. Unlike the
* other addCamera methods, this returns a VideoSink rather than a

View File

@@ -5,6 +5,7 @@
#pragma once
#include <memory>
#include <string_view>
#include <thread>
#include <utility>
@@ -14,9 +15,8 @@ namespace frc {
class CameraServerShared {
public:
virtual ~CameraServerShared() = default;
virtual void ReportUsbCamera(int id) = 0;
virtual void ReportAxisCamera(int id) = 0;
virtual void ReportVideoServer(int id) = 0;
virtual void ReportUsage(std::string_view resource,
std::string_view data) = 0;
virtual void SetCameraServerErrorV(fmt::string_view format,
fmt::format_args args) = 0;
virtual void SetVisionRunnerErrorV(fmt::string_view format,

View File

@@ -17,7 +17,7 @@ macro(add_doxygen_docs)
foreach(dir ${dirs})
list(APPEND docs_dirs ${dir}/src/main/native/include)
file(GLOB dirs ${dir}/src/main/native/thirdparty/*/include)
list(FILTER dirs EXCLUDE REGEX eigen|protobuf)
list(FILTER dirs EXCLUDE REGEX eigen)
set(DOXYGEN_EXCLUDE_PATTERNS "*.pb.h" "**/.clang-tidy" "**/.clang-format")
if(DOCS_WARNINGS_AS_ERRORS)
@@ -45,11 +45,9 @@ macro(add_doxygen_docs)
"wpi/fs.h"
"wpi/FunctionExtras.h"
"wpi/function_ref.h"
"wpi/Hashing.h"
"wpi/iterator.h"
"wpi/iterator_range.h"
"wpi/ManagedStatic.h"
"wpi/MapVector.h"
"wpi/MathExtras.h"
"wpi/MemAlloc.h"
"wpi/PointerIntPair.h"

View File

@@ -2,9 +2,6 @@ include(CompileWarnings)
macro(wpilib_add_test name srcdir)
file(GLOB_RECURSE test_src ${srcdir}/*.cpp)
if(NOT WITH_PROTOBUF)
list(FILTER test_src EXCLUDE REGEX "/proto/")
endif()
add_executable(${name}_test ${test_src})
set_property(TARGET ${name}_test PROPERTY FOLDER "tests")
wpilib_target_warnings(${name}_test)

12
commandsv3/.styleguide Normal file
View File

@@ -0,0 +1,12 @@
modifiableFileExclude {
\.patch$
}
generatedFileExclude {
src/generated/main/java/org/wpilib/commands3/button/
src/generated/main/java/org/wpilib/commands3/proto/
}
repoRootNameOverride {
commandsv3
}

92
commandsv3/BUILD.bazel Normal file
View File

@@ -0,0 +1,92 @@
load("@allwpilib_pip_deps//:requirements.bzl", "requirement")
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_python//python:defs.bzl", "py_binary")
load("//commandsv3:generate.bzl", "generate_commandsv3")
load("//shared/bazel/rules:java_rules.bzl", "wpilib_java_junit5_test", "wpilib_java_library")
py_binary(
name = "generate_files",
srcs = ["generate_files.py"],
target_compatible_with = select({
"@rules_bzlmodrio_toolchains//constraints/is_systemcore:systemcore": ["@platforms//:incompatible"],
"//conditions:default": [],
}),
deps = [requirement("jinja2")],
)
filegroup(
name = "templates",
srcs = glob(["src/generate/main/**"]) + [
"//wpilibj:hid_schema",
],
)
generate_commandsv3(
name = "generate_commandsv3",
)
write_source_files(
name = "write_commandsv3",
files = {
"src/generated": ":generate_commandsv3",
},
suggested_update_target = "//:write_all",
tags = ["pregeneration"],
visibility = ["//visibility:public"],
)
filegroup(
name = "generated_java",
srcs = glob(["src/generated/main/java/**/*.java"]),
)
wpilib_java_library(
name = "commandsv3-java",
srcs = glob(["src/main/java/**/*.java"]) + [":generated_java"],
exported_plugins = ["//javacPlugin:plugin"],
maven_artifact_name = "commands3-java",
maven_group_id = "org.wpilib.commands3",
plugins = ["//javacPlugin:plugin"],
visibility = ["//visibility:public"],
deps = [
"//cscore:cscore-java",
"//hal:hal-java",
"//ntcore:ntcore-java",
"//wpiannotations",
"//wpilibj:wpilibj-java",
"//wpimath:wpimath-java",
"//wpinet:wpinet-java",
"//wpiunits:wpiunits-java",
"//wpiutil:wpiutil-java",
"@maven//:us_hebi_quickbuf_quickbuf_runtime",
],
)
wpilib_java_junit5_test(
name = "commandsv3-java-test",
srcs = glob(["**/*.java"]),
deps = [
":commandsv3-java",
"//hal:hal-java",
"//ntcore:ntcore-java",
"//wpiannotations",
"//wpilibj:wpilibj-java",
"//wpimath:wpimath-java",
"//wpiunits:wpiunits-java",
"//wpiutil:wpiutil-java",
"@maven//:us_hebi_quickbuf_quickbuf_runtime",
],
)
java_binary(
name = "DevMain-Java",
srcs = ["src/dev/java/org/wpilib/commands3/DevMain.java"],
main_class = "org.wpilib.commands3.DevMain",
deps = [
"//hal:hal-java",
"//ntcore:ntcore-java",
"//wpimath:wpimath-java",
"//wpiutil:wpiutil-java",
],
)

53
commandsv3/CMakeLists.txt Normal file
View File

@@ -0,0 +1,53 @@
project(commandsv3)
include(SubDirList)
include(CompileWarnings)
include(AddTest)
if(WITH_JAVA)
include(UseJava)
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java src/generated/main/java/*.java)
file(GLOB QUICKBUF_JAR ${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar)
add_jar(
commandsv3_jar
${JAVA_SOURCES}
INCLUDE_JARS
datalog_jar
hal_jar
ntcore_jar
cscore_jar
cameraserver_jar
wpiannotations_jar
wpimath_jar
wpiunits_jar
wpiutil_jar
wpilibj_jar
${QUICKBUF_JAR}
OUTPUT_NAME commandsv3
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
)
install_jar(commandsv3_jar DESTINATION ${java_lib_dest})
install_jar_exports(
TARGETS commandsv3_jar
FILE commandsv3_jar.cmake
DESTINATION share/commandsv3
)
endif()
if(WITH_JAVA_SOURCE)
include(UseJava)
include(CreateSourceJar)
add_source_jar(
commandsv3_src_jar
BASE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}/src/main/java
${CMAKE_CURRENT_SOURCE_DIR}/src/generated/main/java
OUTPUT_NAME commandsv3-sources
OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest}
)
set_property(TARGET commandsv3_src_jar PROPERTY FOLDER "java")
install_jar(commandsv3_src_jar DESTINATION ${java_lib_dest})
endif()

View File

@@ -0,0 +1,18 @@
{
"fileName": "CommandsV3.json",
"name": "Commands v3",
"version": "1.0.0",
"uuid": "4decdc05-a056-46cf-9561-39449bbb0130",
"frcYear": "2027_alpha1",
"mavenUrls": [],
"jsonUrl": "",
"javaDependencies": [
{
"groupId": "org.wpilib.commands3",
"artifactId": "commands3-java",
"version": "wpilib"
}
],
"jniDependencies": [],
"cppDependencies": []
}

49
commandsv3/build.gradle Normal file
View File

@@ -0,0 +1,49 @@
ext {
useJava = true
useCpp = false
baseId = 'commands3'
groupId = 'org.wpilib'
nativeName = 'commands3'
devMain = 'org.wpilib.commands3.DevMain'
}
apply from: "${rootDir}/shared/java/javacommon.gradle"
evaluationDependsOn(':wpiutil')
evaluationDependsOn(':ntcore')
evaluationDependsOn(':hal')
evaluationDependsOn(':wpimath')
evaluationDependsOn(':wpilibj')
dependencies {
annotationProcessor project(':javacPlugin')
implementation project(':wpiannotations')
implementation project(':wpiutil')
implementation project(':wpinet')
implementation project(':ntcore')
implementation project(':hal')
implementation project(':wpimath')
implementation project(':wpilibj')
api("us.hebi.quickbuf:quickbuf-runtime:1.4")
testAnnotationProcessor project(':javacPlugin')
testImplementation 'org.mockito:mockito-core:4.1.0'
}
sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"
sourceSets.main.resources.srcDir "${projectDir}/src/main/proto"
test {
testLogging {
outputs.upToDateWhen {false}
showStandardStreams = true
}
// For reflective access to the continuation classes
jvmArgs += [
'--add-opens',
'java.base/jdk.internal.vm=ALL-UNNAMED',
'--add-opens',
'java.base/java.lang=ALL-UNNAMED',
]
}

View File

@@ -0,0 +1,15 @@
include(CMakeFindDependencyMacro)
@WPIUTIL_DEP_REPLACE@
@DATALOG_DEP_REPLACE@
@NTCORE_DEP_REPLACE@
@CSCORE_DEP_REPLACE@
@CAMERASERVER_DEP_REPLACE@
@HAL_DEP_REPLACE@
@WPILIBC_DEP_REPLACE@
@WPIMATH_DEP_REPLACE@
@FILENAME_DEP_REPLACE@
include(${SELF_DIR}/commandsv3.cmake)
if(@WITH_JAVA@)
include(${SELF_DIR}/commandsv3_jar.cmake)
endif()

45
commandsv3/generate.bzl Normal file
View File

@@ -0,0 +1,45 @@
def __generate_commandsv3_impl(ctx):
"""
Custom rule used to create the commandsv3 pre-generated files. See `./README-Bazel.md` for the reasoning.
"""
output_dir = ctx.actions.declare_directory("_gendir")
args = ctx.actions.args()
args.add("--output_directory", output_dir.path)
args.add("--template_root", "commandsv3/src/generate")
args.add("--protoc", ctx.executable._protoc)
args.add("--quickbuf_plugin", ctx.executable._quickbuf)
ctx.actions.run(
inputs = ctx.attr._templates.files,
outputs = [output_dir],
executable = ctx.executable._tool,
arguments = [args],
tools = [ctx.executable._protoc, ctx.executable._quickbuf],
)
return [DefaultInfo(files = depset([output_dir]))]
generate_commandsv3 = rule(
implementation = __generate_commandsv3_impl,
attrs = {
"_protoc": attr.label(
default = Label("@com_google_protobuf//:protoc"),
cfg = "exec",
executable = True,
),
"_quickbuf": attr.label(
default = Label("//:quickbuf_protoc"),
cfg = "exec",
executable = True,
),
"_templates": attr.label(
default = Label("//commandsv3:templates"),
),
"_tool": attr.label(
default = Label("//commandsv3:generate_files"),
cfg = "exec",
executable = True,
),
},
)

120
commandsv3/generate_files.py Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# 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.
import argparse
import json
import subprocess
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
def write_controller_file(output_dir: Path, controller_name: str, contents: str):
output_dir.mkdir(parents=True, exist_ok=True)
output_file = output_dir / controller_name
output_file.write_text(contents, encoding="utf-8", newline="\n")
print("Writing to ", output_file)
def generate_hids(output_directory: Path, template_directory: Path, schema_file: Path):
with schema_file.open(encoding="utf-8") as f:
controllers = json.load(f)
# Java files
java_subdirectory = "main/java/org/wpilib/commands3/button"
env = Environment(
loader=FileSystemLoader(template_directory / "main/java"),
autoescape=False,
keep_trailing_newline=True,
)
root_path = output_directory / java_subdirectory
template = env.get_template("commandhid.java.jinja")
for controller in controllers:
controllerName = f"Command{controller['ConsoleName']}Controller.java"
output = template.render(controller)
write_controller_file(root_path, controllerName, output)
def generate_quickbuf(
protoc, quickbuf_plugin: Path, output_directory: Path, proto_dir: Path
):
proto_files = proto_dir.glob("*.proto")
for path in proto_files:
absolute_filename = path.absolute()
args = [protoc]
if quickbuf_plugin:
# Optional on macOS if using protoc-quickbuf
args += [f"--plugin=protoc-gen-quickbuf={quickbuf_plugin}"]
args += [
f"--quickbuf_out=gen_descriptors=true:{output_directory.absolute()}",
f"-I{absolute_filename.parent}",
absolute_filename,
]
subprocess.check_call(args)
java_files = (output_directory / "org/wpilib/commands3/proto").glob("*.java")
for java_file in java_files:
with (java_file).open(encoding="utf-8") as f:
content = f.read()
java_file.write_text(
"// Copyright (c) FIRST and other WPILib contributors.\n// Open Source Software; you can modify and/or share it under the terms of\n// the WPILib BSD license file in the root directory of this project.\n"
+ content,
encoding="utf-8",
newline="\n",
)
def main():
script_path = Path(__file__).resolve()
dirname = script_path.parent
parser = argparse.ArgumentParser()
parser.add_argument(
"--output_directory",
help="Optional. If set, will output the generated files to this directory, otherwise it will use a path relative to the script",
default=dirname / "src/generated",
type=Path,
)
parser.add_argument(
"--template_root",
help="Optional. If set, will use this directory as the root for the jinja templates",
default=dirname / "src/generate",
type=Path,
)
parser.add_argument(
"--schema_file",
help="Optional. If set, will use this file for the joystick schema",
default="wpilibj/src/generate/hids.json",
type=Path,
)
parser.add_argument(
"--protoc",
help="Protoc executable command",
default="protoc",
)
parser.add_argument(
"--quickbuf_plugin",
help="Path to the quickbuf protoc plugin",
)
parser.add_argument(
"--proto_directory",
help="Optional. If set, will use this directory to glob for protobuf files",
default=dirname / "src/main/proto",
type=Path,
)
args = parser.parse_args()
generate_hids(args.output_directory, args.template_root, args.schema_file)
generate_quickbuf(
args.protoc,
args.quickbuf_plugin,
args.output_directory / "main/java",
args.proto_directory,
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,23 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import edu.wpi.first.hal.HALUtil;
import edu.wpi.first.networktables.NetworkTablesJNI;
import edu.wpi.first.util.CombinedRuntimeLoader;
/** Dev main. */
public final class DevMain {
/** Main entry point. */
public static void main(String[] args) {
System.out.println("Commands V3 DevMain");
System.out.println(CombinedRuntimeLoader.getPlatformPath());
System.out.println(NetworkTablesJNI.now());
System.out.println(HALUtil.getHALRuntimeType());
}
private DevMain() {}
}

View File

@@ -0,0 +1,148 @@
// 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.
// THIS FILE WAS AUTO-GENERATED BY ./commandsv3/generate_files.py. DO NOT MODIFY
{% macro capitalize_first(string) -%}
{{ string[0]|capitalize + string[1:] }}
{%- endmacro %}
package org.wpilib.commands3.button;
import edu.wpi.first.wpilibj.{{ ConsoleName }}Controller;
import edu.wpi.first.wpilibj.event.EventLoop;
import org.wpilib.commands3.Scheduler;
import org.wpilib.commands3.Trigger;
/**
* A version of {@link {{ ConsoleName }}Controller} with {@link Trigger} factories for command-based.
*
* @see {{ ConsoleName }}Controller
*/
@SuppressWarnings("MethodName")
public class Command{{ ConsoleName }}Controller extends CommandGenericHID {
private final {{ ConsoleName }}Controller m_hid;
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the {@link Scheduler#getDefault() default scheduler} using its default event loop.
*
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public Command{{ ConsoleName }}Controller(int port) {
super(port);
m_hid = new {{ ConsoleName }}Controller(port);
}
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the given scheduler using its default event loop.
*
* @param scheduler The scheduler that should execute the triggered commands.
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public Command{{ ConsoleName }}Controller(Scheduler scheduler, int port) {
super(scheduler, port);
m_hid = new {{ ConsoleName }}Controller(port);
}
/**
* Get the underlying GenericHID object.
*
* @return the wrapped GenericHID object
*/
@Override
public {{ ConsoleName }}Controller getHID() {
return m_hid;
}
{% for button in buttons %}
/**
* Constructs a Trigger instance around the {{ button.DocName|default(button.name) }} button's digital signal.
*
* @return a Trigger instance representing the {{ button.DocName|default(button.name) }} button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #{{ button.name }}(EventLoop)
*/
public Trigger {{ button.name }}() {
return {{ button.name }}(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the {{ button.DocName|default(button.name) }} button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the {{ button.DocName|default(button.name) }} button's digital signal attached
* to the given loop.
*/
public Trigger {{ button.name }}(EventLoop loop) {
return button({{ ConsoleName }}Controller.Button.k{{ capitalize_first(button.name) }}.value, loop);
}
{% endfor -%}
{% for trigger in triggers -%}
{% if trigger.UseThresholdMethods %}
/**
* Constructs a Trigger instance around the axis value of the {{ trigger.DocName }}. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @param loop the event loop instance to attach the Trigger to.
* @return a Trigger instance that is true when the {{ trigger.DocName }}'s axis exceeds the provided
* threshold, attached to the given event loop
*/
public Trigger {{ trigger.name }}(double threshold, EventLoop loop) {
return axisGreaterThan({{ ConsoleName }}Controller.Axis.k{{ capitalize_first(trigger.name) }}.value, threshold, loop);
}
/**
* Constructs a Trigger instance around the axis value of the {{ trigger.DocName }}. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @return a Trigger instance that is true when the {{ trigger.DocName }}'s axis exceeds the provided
* threshold, attached to the {@link Scheduler#getDefaultEventLoop() default scheduler event
* loop} on the scheduler passed to the controller's constructor, or the {@link
* Scheduler#getDefault default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger {{ trigger.name }}(double threshold) {
return {{ trigger.name }}(threshold, getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the axis value of the {{ trigger.DocName }}. The returned trigger
* will be true when the axis value is greater than 0.5.
*
* @return a Trigger instance that is true when the {{ trigger.DocName }}'s axis exceeds 0.5, attached to
* the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger {{ trigger.name }}() {
return {{ trigger.name }}(0.5);
}
{% endif -%}
{% endfor -%}
{% for stick in sticks %}
/**
* Get the {{ stick.NameParts[1] }} axis value of {{ stick.NameParts[0] }} side of the controller. {{ stick.PositiveDirection }} is positive.
*
* @return The axis value.
*/
public double get{{ stick.NameParts|map("capitalize")|join }}() {
return m_hid.get{{ stick.NameParts|map("capitalize")|join }}();
}
{% endfor -%}
{% for trigger in triggers %}
/**
* Get the {{ trigger.DocName }} axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double get{{ capitalize_first(trigger.name) }}Axis() {
return m_hid.get{{ capitalize_first(trigger.name) }}Axis();
}
{% endfor -%}
}

View File

@@ -0,0 +1,447 @@
// 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.
// THIS FILE WAS AUTO-GENERATED BY ./commandsv3/generate_files.py. DO NOT MODIFY
package org.wpilib.commands3.button;
import edu.wpi.first.wpilibj.PS4Controller;
import edu.wpi.first.wpilibj.event.EventLoop;
import org.wpilib.commands3.Scheduler;
import org.wpilib.commands3.Trigger;
/**
* A version of {@link PS4Controller} with {@link Trigger} factories for command-based.
*
* @see PS4Controller
*/
@SuppressWarnings("MethodName")
public class CommandPS4Controller extends CommandGenericHID {
private final PS4Controller m_hid;
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the {@link Scheduler#getDefault() default scheduler} using its default event loop.
*
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandPS4Controller(int port) {
super(port);
m_hid = new PS4Controller(port);
}
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the given scheduler using its default event loop.
*
* @param scheduler The scheduler that should execute the triggered commands.
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandPS4Controller(Scheduler scheduler, int port) {
super(scheduler, port);
m_hid = new PS4Controller(port);
}
/**
* Get the underlying GenericHID object.
*
* @return the wrapped GenericHID object
*/
@Override
public PS4Controller getHID() {
return m_hid;
}
/**
* Constructs a Trigger instance around the square button's digital signal.
*
* @return a Trigger instance representing the square button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #square(EventLoop)
*/
public Trigger square() {
return square(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the square button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the square button's digital signal attached
* to the given loop.
*/
public Trigger square(EventLoop loop) {
return button(PS4Controller.Button.kSquare.value, loop);
}
/**
* Constructs a Trigger instance around the cross button's digital signal.
*
* @return a Trigger instance representing the cross button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #cross(EventLoop)
*/
public Trigger cross() {
return cross(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the cross button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the cross button's digital signal attached
* to the given loop.
*/
public Trigger cross(EventLoop loop) {
return button(PS4Controller.Button.kCross.value, loop);
}
/**
* Constructs a Trigger instance around the circle button's digital signal.
*
* @return a Trigger instance representing the circle button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #circle(EventLoop)
*/
public Trigger circle() {
return circle(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the circle button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the circle button's digital signal attached
* to the given loop.
*/
public Trigger circle(EventLoop loop) {
return button(PS4Controller.Button.kCircle.value, loop);
}
/**
* Constructs a Trigger instance around the triangle button's digital signal.
*
* @return a Trigger instance representing the triangle button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #triangle(EventLoop)
*/
public Trigger triangle() {
return triangle(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the triangle button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the triangle button's digital signal attached
* to the given loop.
*/
public Trigger triangle(EventLoop loop) {
return button(PS4Controller.Button.kTriangle.value, loop);
}
/**
* Constructs a Trigger instance around the left trigger 1 button's digital signal.
*
* @return a Trigger instance representing the left trigger 1 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L1(EventLoop)
*/
public Trigger L1() {
return L1(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left trigger 1 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left trigger 1 button's digital signal attached
* to the given loop.
*/
public Trigger L1(EventLoop loop) {
return button(PS4Controller.Button.kL1.value, loop);
}
/**
* Constructs a Trigger instance around the right trigger 1 button's digital signal.
*
* @return a Trigger instance representing the right trigger 1 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R1(EventLoop)
*/
public Trigger R1() {
return R1(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right trigger 1 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right trigger 1 button's digital signal attached
* to the given loop.
*/
public Trigger R1(EventLoop loop) {
return button(PS4Controller.Button.kR1.value, loop);
}
/**
* Constructs a Trigger instance around the left trigger 2 button's digital signal.
*
* @return a Trigger instance representing the left trigger 2 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L2(EventLoop)
*/
public Trigger L2() {
return L2(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left trigger 2 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left trigger 2 button's digital signal attached
* to the given loop.
*/
public Trigger L2(EventLoop loop) {
return button(PS4Controller.Button.kL2.value, loop);
}
/**
* Constructs a Trigger instance around the right trigger 2 button's digital signal.
*
* @return a Trigger instance representing the right trigger 2 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R2(EventLoop)
*/
public Trigger R2() {
return R2(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right trigger 2 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right trigger 2 button's digital signal attached
* to the given loop.
*/
public Trigger R2(EventLoop loop) {
return button(PS4Controller.Button.kR2.value, loop);
}
/**
* Constructs a Trigger instance around the share button's digital signal.
*
* @return a Trigger instance representing the share button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #share(EventLoop)
*/
public Trigger share() {
return share(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the share button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the share button's digital signal attached
* to the given loop.
*/
public Trigger share(EventLoop loop) {
return button(PS4Controller.Button.kShare.value, loop);
}
/**
* Constructs a Trigger instance around the options button's digital signal.
*
* @return a Trigger instance representing the options button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #options(EventLoop)
*/
public Trigger options() {
return options(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the options button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the options button's digital signal attached
* to the given loop.
*/
public Trigger options(EventLoop loop) {
return button(PS4Controller.Button.kOptions.value, loop);
}
/**
* Constructs a Trigger instance around the L3 (left stick) button's digital signal.
*
* @return a Trigger instance representing the L3 (left stick) button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L3(EventLoop)
*/
public Trigger L3() {
return L3(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the L3 (left stick) button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the L3 (left stick) button's digital signal attached
* to the given loop.
*/
public Trigger L3(EventLoop loop) {
return button(PS4Controller.Button.kL3.value, loop);
}
/**
* Constructs a Trigger instance around the R3 (right stick) button's digital signal.
*
* @return a Trigger instance representing the R3 (right stick) button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R3(EventLoop)
*/
public Trigger R3() {
return R3(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the R3 (right stick) button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the R3 (right stick) button's digital signal attached
* to the given loop.
*/
public Trigger R3(EventLoop loop) {
return button(PS4Controller.Button.kR3.value, loop);
}
/**
* Constructs a Trigger instance around the PlayStation button's digital signal.
*
* @return a Trigger instance representing the PlayStation button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #PS(EventLoop)
*/
public Trigger PS() {
return PS(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the PlayStation button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the PlayStation button's digital signal attached
* to the given loop.
*/
public Trigger PS(EventLoop loop) {
return button(PS4Controller.Button.kPS.value, loop);
}
/**
* Constructs a Trigger instance around the touchpad button's digital signal.
*
* @return a Trigger instance representing the touchpad button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #touchpad(EventLoop)
*/
public Trigger touchpad() {
return touchpad(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the touchpad button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the touchpad button's digital signal attached
* to the given loop.
*/
public Trigger touchpad(EventLoop loop) {
return button(PS4Controller.Button.kTouchpad.value, loop);
}
/**
* Get the X axis value of left side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getLeftX() {
return m_hid.getLeftX();
}
/**
* Get the Y axis value of left side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getLeftY() {
return m_hid.getLeftY();
}
/**
* Get the X axis value of right side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getRightX() {
return m_hid.getRightX();
}
/**
* Get the Y axis value of right side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getRightY() {
return m_hid.getRightY();
}
/**
* Get the left trigger 2 axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getL2Axis() {
return m_hid.getL2Axis();
}
/**
* Get the right trigger 2 axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getR2Axis() {
return m_hid.getR2Axis();
}
}

View File

@@ -0,0 +1,447 @@
// 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.
// THIS FILE WAS AUTO-GENERATED BY ./commandsv3/generate_files.py. DO NOT MODIFY
package org.wpilib.commands3.button;
import edu.wpi.first.wpilibj.PS5Controller;
import edu.wpi.first.wpilibj.event.EventLoop;
import org.wpilib.commands3.Scheduler;
import org.wpilib.commands3.Trigger;
/**
* A version of {@link PS5Controller} with {@link Trigger} factories for command-based.
*
* @see PS5Controller
*/
@SuppressWarnings("MethodName")
public class CommandPS5Controller extends CommandGenericHID {
private final PS5Controller m_hid;
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the {@link Scheduler#getDefault() default scheduler} using its default event loop.
*
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandPS5Controller(int port) {
super(port);
m_hid = new PS5Controller(port);
}
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the given scheduler using its default event loop.
*
* @param scheduler The scheduler that should execute the triggered commands.
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandPS5Controller(Scheduler scheduler, int port) {
super(scheduler, port);
m_hid = new PS5Controller(port);
}
/**
* Get the underlying GenericHID object.
*
* @return the wrapped GenericHID object
*/
@Override
public PS5Controller getHID() {
return m_hid;
}
/**
* Constructs a Trigger instance around the square button's digital signal.
*
* @return a Trigger instance representing the square button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #square(EventLoop)
*/
public Trigger square() {
return square(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the square button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the square button's digital signal attached
* to the given loop.
*/
public Trigger square(EventLoop loop) {
return button(PS5Controller.Button.kSquare.value, loop);
}
/**
* Constructs a Trigger instance around the cross button's digital signal.
*
* @return a Trigger instance representing the cross button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #cross(EventLoop)
*/
public Trigger cross() {
return cross(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the cross button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the cross button's digital signal attached
* to the given loop.
*/
public Trigger cross(EventLoop loop) {
return button(PS5Controller.Button.kCross.value, loop);
}
/**
* Constructs a Trigger instance around the circle button's digital signal.
*
* @return a Trigger instance representing the circle button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #circle(EventLoop)
*/
public Trigger circle() {
return circle(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the circle button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the circle button's digital signal attached
* to the given loop.
*/
public Trigger circle(EventLoop loop) {
return button(PS5Controller.Button.kCircle.value, loop);
}
/**
* Constructs a Trigger instance around the triangle button's digital signal.
*
* @return a Trigger instance representing the triangle button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #triangle(EventLoop)
*/
public Trigger triangle() {
return triangle(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the triangle button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the triangle button's digital signal attached
* to the given loop.
*/
public Trigger triangle(EventLoop loop) {
return button(PS5Controller.Button.kTriangle.value, loop);
}
/**
* Constructs a Trigger instance around the left trigger 1 button's digital signal.
*
* @return a Trigger instance representing the left trigger 1 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L1(EventLoop)
*/
public Trigger L1() {
return L1(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left trigger 1 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left trigger 1 button's digital signal attached
* to the given loop.
*/
public Trigger L1(EventLoop loop) {
return button(PS5Controller.Button.kL1.value, loop);
}
/**
* Constructs a Trigger instance around the right trigger 1 button's digital signal.
*
* @return a Trigger instance representing the right trigger 1 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R1(EventLoop)
*/
public Trigger R1() {
return R1(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right trigger 1 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right trigger 1 button's digital signal attached
* to the given loop.
*/
public Trigger R1(EventLoop loop) {
return button(PS5Controller.Button.kR1.value, loop);
}
/**
* Constructs a Trigger instance around the left trigger 2 button's digital signal.
*
* @return a Trigger instance representing the left trigger 2 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L2(EventLoop)
*/
public Trigger L2() {
return L2(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left trigger 2 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left trigger 2 button's digital signal attached
* to the given loop.
*/
public Trigger L2(EventLoop loop) {
return button(PS5Controller.Button.kL2.value, loop);
}
/**
* Constructs a Trigger instance around the right trigger 2 button's digital signal.
*
* @return a Trigger instance representing the right trigger 2 button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R2(EventLoop)
*/
public Trigger R2() {
return R2(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right trigger 2 button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right trigger 2 button's digital signal attached
* to the given loop.
*/
public Trigger R2(EventLoop loop) {
return button(PS5Controller.Button.kR2.value, loop);
}
/**
* Constructs a Trigger instance around the create button's digital signal.
*
* @return a Trigger instance representing the create button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #create(EventLoop)
*/
public Trigger create() {
return create(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the create button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the create button's digital signal attached
* to the given loop.
*/
public Trigger create(EventLoop loop) {
return button(PS5Controller.Button.kCreate.value, loop);
}
/**
* Constructs a Trigger instance around the options button's digital signal.
*
* @return a Trigger instance representing the options button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #options(EventLoop)
*/
public Trigger options() {
return options(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the options button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the options button's digital signal attached
* to the given loop.
*/
public Trigger options(EventLoop loop) {
return button(PS5Controller.Button.kOptions.value, loop);
}
/**
* Constructs a Trigger instance around the L3 (left stick) button's digital signal.
*
* @return a Trigger instance representing the L3 (left stick) button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #L3(EventLoop)
*/
public Trigger L3() {
return L3(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the L3 (left stick) button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the L3 (left stick) button's digital signal attached
* to the given loop.
*/
public Trigger L3(EventLoop loop) {
return button(PS5Controller.Button.kL3.value, loop);
}
/**
* Constructs a Trigger instance around the R3 (right stick) button's digital signal.
*
* @return a Trigger instance representing the R3 (right stick) button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #R3(EventLoop)
*/
public Trigger R3() {
return R3(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the R3 (right stick) button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the R3 (right stick) button's digital signal attached
* to the given loop.
*/
public Trigger R3(EventLoop loop) {
return button(PS5Controller.Button.kR3.value, loop);
}
/**
* Constructs a Trigger instance around the PlayStation button's digital signal.
*
* @return a Trigger instance representing the PlayStation button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #PS(EventLoop)
*/
public Trigger PS() {
return PS(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the PlayStation button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the PlayStation button's digital signal attached
* to the given loop.
*/
public Trigger PS(EventLoop loop) {
return button(PS5Controller.Button.kPS.value, loop);
}
/**
* Constructs a Trigger instance around the touchpad button's digital signal.
*
* @return a Trigger instance representing the touchpad button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #touchpad(EventLoop)
*/
public Trigger touchpad() {
return touchpad(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the touchpad button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the touchpad button's digital signal attached
* to the given loop.
*/
public Trigger touchpad(EventLoop loop) {
return button(PS5Controller.Button.kTouchpad.value, loop);
}
/**
* Get the X axis value of left side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getLeftX() {
return m_hid.getLeftX();
}
/**
* Get the Y axis value of left side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getLeftY() {
return m_hid.getLeftY();
}
/**
* Get the X axis value of right side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getRightX() {
return m_hid.getRightX();
}
/**
* Get the Y axis value of right side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getRightY() {
return m_hid.getRightY();
}
/**
* Get the left trigger 2 axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getL2Axis() {
return m_hid.getL2Axis();
}
/**
* Get the right trigger 2 axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getR2Axis() {
return m_hid.getR2Axis();
}
}

View File

@@ -0,0 +1,451 @@
// 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.
// THIS FILE WAS AUTO-GENERATED BY ./commandsv3/generate_files.py. DO NOT MODIFY
package org.wpilib.commands3.button;
import edu.wpi.first.wpilibj.StadiaController;
import edu.wpi.first.wpilibj.event.EventLoop;
import org.wpilib.commands3.Scheduler;
import org.wpilib.commands3.Trigger;
/**
* A version of {@link StadiaController} with {@link Trigger} factories for command-based.
*
* @see StadiaController
*/
@SuppressWarnings("MethodName")
public class CommandStadiaController extends CommandGenericHID {
private final StadiaController m_hid;
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the {@link Scheduler#getDefault() default scheduler} using its default event loop.
*
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandStadiaController(int port) {
super(port);
m_hid = new StadiaController(port);
}
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the given scheduler using its default event loop.
*
* @param scheduler The scheduler that should execute the triggered commands.
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandStadiaController(Scheduler scheduler, int port) {
super(scheduler, port);
m_hid = new StadiaController(port);
}
/**
* Get the underlying GenericHID object.
*
* @return the wrapped GenericHID object
*/
@Override
public StadiaController getHID() {
return m_hid;
}
/**
* Constructs a Trigger instance around the A button's digital signal.
*
* @return a Trigger instance representing the A button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #a(EventLoop)
*/
public Trigger a() {
return a(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the A button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the A button's digital signal attached
* to the given loop.
*/
public Trigger a(EventLoop loop) {
return button(StadiaController.Button.kA.value, loop);
}
/**
* Constructs a Trigger instance around the B button's digital signal.
*
* @return a Trigger instance representing the B button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #b(EventLoop)
*/
public Trigger b() {
return b(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the B button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the B button's digital signal attached
* to the given loop.
*/
public Trigger b(EventLoop loop) {
return button(StadiaController.Button.kB.value, loop);
}
/**
* Constructs a Trigger instance around the X button's digital signal.
*
* @return a Trigger instance representing the X button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #x(EventLoop)
*/
public Trigger x() {
return x(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the X button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the X button's digital signal attached
* to the given loop.
*/
public Trigger x(EventLoop loop) {
return button(StadiaController.Button.kX.value, loop);
}
/**
* Constructs a Trigger instance around the Y button's digital signal.
*
* @return a Trigger instance representing the Y button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #y(EventLoop)
*/
public Trigger y() {
return y(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the Y button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the Y button's digital signal attached
* to the given loop.
*/
public Trigger y(EventLoop loop) {
return button(StadiaController.Button.kY.value, loop);
}
/**
* Constructs a Trigger instance around the left bumper button's digital signal.
*
* @return a Trigger instance representing the left bumper button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #leftBumper(EventLoop)
*/
public Trigger leftBumper() {
return leftBumper(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left bumper button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left bumper button's digital signal attached
* to the given loop.
*/
public Trigger leftBumper(EventLoop loop) {
return button(StadiaController.Button.kLeftBumper.value, loop);
}
/**
* Constructs a Trigger instance around the right bumper button's digital signal.
*
* @return a Trigger instance representing the right bumper button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #rightBumper(EventLoop)
*/
public Trigger rightBumper() {
return rightBumper(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right bumper button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right bumper button's digital signal attached
* to the given loop.
*/
public Trigger rightBumper(EventLoop loop) {
return button(StadiaController.Button.kRightBumper.value, loop);
}
/**
* Constructs a Trigger instance around the left stick button's digital signal.
*
* @return a Trigger instance representing the left stick button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #leftStick(EventLoop)
*/
public Trigger leftStick() {
return leftStick(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left stick button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left stick button's digital signal attached
* to the given loop.
*/
public Trigger leftStick(EventLoop loop) {
return button(StadiaController.Button.kLeftStick.value, loop);
}
/**
* Constructs a Trigger instance around the right stick button's digital signal.
*
* @return a Trigger instance representing the right stick button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #rightStick(EventLoop)
*/
public Trigger rightStick() {
return rightStick(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right stick button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right stick button's digital signal attached
* to the given loop.
*/
public Trigger rightStick(EventLoop loop) {
return button(StadiaController.Button.kRightStick.value, loop);
}
/**
* Constructs a Trigger instance around the ellipses button's digital signal.
*
* @return a Trigger instance representing the ellipses button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #ellipses(EventLoop)
*/
public Trigger ellipses() {
return ellipses(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the ellipses button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the ellipses button's digital signal attached
* to the given loop.
*/
public Trigger ellipses(EventLoop loop) {
return button(StadiaController.Button.kEllipses.value, loop);
}
/**
* Constructs a Trigger instance around the hamburger button's digital signal.
*
* @return a Trigger instance representing the hamburger button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #hamburger(EventLoop)
*/
public Trigger hamburger() {
return hamburger(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the hamburger button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the hamburger button's digital signal attached
* to the given loop.
*/
public Trigger hamburger(EventLoop loop) {
return button(StadiaController.Button.kHamburger.value, loop);
}
/**
* Constructs a Trigger instance around the stadia button's digital signal.
*
* @return a Trigger instance representing the stadia button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #stadia(EventLoop)
*/
public Trigger stadia() {
return stadia(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the stadia button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the stadia button's digital signal attached
* to the given loop.
*/
public Trigger stadia(EventLoop loop) {
return button(StadiaController.Button.kStadia.value, loop);
}
/**
* Constructs a Trigger instance around the right trigger button's digital signal.
*
* @return a Trigger instance representing the right trigger button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #rightTrigger(EventLoop)
*/
public Trigger rightTrigger() {
return rightTrigger(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right trigger button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right trigger button's digital signal attached
* to the given loop.
*/
public Trigger rightTrigger(EventLoop loop) {
return button(StadiaController.Button.kRightTrigger.value, loop);
}
/**
* Constructs a Trigger instance around the left trigger button's digital signal.
*
* @return a Trigger instance representing the left trigger button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #leftTrigger(EventLoop)
*/
public Trigger leftTrigger() {
return leftTrigger(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left trigger button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left trigger button's digital signal attached
* to the given loop.
*/
public Trigger leftTrigger(EventLoop loop) {
return button(StadiaController.Button.kLeftTrigger.value, loop);
}
/**
* Constructs a Trigger instance around the google button's digital signal.
*
* @return a Trigger instance representing the google button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #google(EventLoop)
*/
public Trigger google() {
return google(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the google button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the google button's digital signal attached
* to the given loop.
*/
public Trigger google(EventLoop loop) {
return button(StadiaController.Button.kGoogle.value, loop);
}
/**
* Constructs a Trigger instance around the frame button's digital signal.
*
* @return a Trigger instance representing the frame button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #frame(EventLoop)
*/
public Trigger frame() {
return frame(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the frame button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the frame button's digital signal attached
* to the given loop.
*/
public Trigger frame(EventLoop loop) {
return button(StadiaController.Button.kFrame.value, loop);
}
/**
* Get the X axis value of left side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getLeftX() {
return m_hid.getLeftX();
}
/**
* Get the X axis value of right side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getRightX() {
return m_hid.getRightX();
}
/**
* Get the Y axis value of left side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getLeftY() {
return m_hid.getLeftY();
}
/**
* Get the Y axis value of right side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getRightY() {
return m_hid.getRightY();
}
}

View File

@@ -0,0 +1,435 @@
// 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.
// THIS FILE WAS AUTO-GENERATED BY ./commandsv3/generate_files.py. DO NOT MODIFY
package org.wpilib.commands3.button;
import edu.wpi.first.wpilibj.XboxController;
import edu.wpi.first.wpilibj.event.EventLoop;
import org.wpilib.commands3.Scheduler;
import org.wpilib.commands3.Trigger;
/**
* A version of {@link XboxController} with {@link Trigger} factories for command-based.
*
* @see XboxController
*/
@SuppressWarnings("MethodName")
public class CommandXboxController extends CommandGenericHID {
private final XboxController m_hid;
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the {@link Scheduler#getDefault() default scheduler} using its default event loop.
*
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandXboxController(int port) {
super(port);
m_hid = new XboxController(port);
}
/**
* Construct an instance of a controller. Commands bound to buttons on the controller will be
* scheduled on the given scheduler using its default event loop.
*
* @param scheduler The scheduler that should execute the triggered commands.
* @param port The port index on the Driver Station that the controller is plugged into.
*/
public CommandXboxController(Scheduler scheduler, int port) {
super(scheduler, port);
m_hid = new XboxController(port);
}
/**
* Get the underlying GenericHID object.
*
* @return the wrapped GenericHID object
*/
@Override
public XboxController getHID() {
return m_hid;
}
/**
* Constructs a Trigger instance around the A button's digital signal.
*
* @return a Trigger instance representing the A button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #a(EventLoop)
*/
public Trigger a() {
return a(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the A button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the A button's digital signal attached
* to the given loop.
*/
public Trigger a(EventLoop loop) {
return button(XboxController.Button.kA.value, loop);
}
/**
* Constructs a Trigger instance around the B button's digital signal.
*
* @return a Trigger instance representing the B button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #b(EventLoop)
*/
public Trigger b() {
return b(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the B button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the B button's digital signal attached
* to the given loop.
*/
public Trigger b(EventLoop loop) {
return button(XboxController.Button.kB.value, loop);
}
/**
* Constructs a Trigger instance around the X button's digital signal.
*
* @return a Trigger instance representing the X button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #x(EventLoop)
*/
public Trigger x() {
return x(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the X button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the X button's digital signal attached
* to the given loop.
*/
public Trigger x(EventLoop loop) {
return button(XboxController.Button.kX.value, loop);
}
/**
* Constructs a Trigger instance around the Y button's digital signal.
*
* @return a Trigger instance representing the Y button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #y(EventLoop)
*/
public Trigger y() {
return y(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the Y button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the Y button's digital signal attached
* to the given loop.
*/
public Trigger y(EventLoop loop) {
return button(XboxController.Button.kY.value, loop);
}
/**
* Constructs a Trigger instance around the left bumper button's digital signal.
*
* @return a Trigger instance representing the left bumper button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #leftBumper(EventLoop)
*/
public Trigger leftBumper() {
return leftBumper(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left bumper button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left bumper button's digital signal attached
* to the given loop.
*/
public Trigger leftBumper(EventLoop loop) {
return button(XboxController.Button.kLeftBumper.value, loop);
}
/**
* Constructs a Trigger instance around the right bumper button's digital signal.
*
* @return a Trigger instance representing the right bumper button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #rightBumper(EventLoop)
*/
public Trigger rightBumper() {
return rightBumper(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right bumper button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right bumper button's digital signal attached
* to the given loop.
*/
public Trigger rightBumper(EventLoop loop) {
return button(XboxController.Button.kRightBumper.value, loop);
}
/**
* Constructs a Trigger instance around the back button's digital signal.
*
* @return a Trigger instance representing the back button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #back(EventLoop)
*/
public Trigger back() {
return back(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the back button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the back button's digital signal attached
* to the given loop.
*/
public Trigger back(EventLoop loop) {
return button(XboxController.Button.kBack.value, loop);
}
/**
* Constructs a Trigger instance around the start button's digital signal.
*
* @return a Trigger instance representing the start button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #start(EventLoop)
*/
public Trigger start() {
return start(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the start button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the start button's digital signal attached
* to the given loop.
*/
public Trigger start(EventLoop loop) {
return button(XboxController.Button.kStart.value, loop);
}
/**
* Constructs a Trigger instance around the left stick button's digital signal.
*
* @return a Trigger instance representing the left stick button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #leftStick(EventLoop)
*/
public Trigger leftStick() {
return leftStick(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the left stick button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the left stick button's digital signal attached
* to the given loop.
*/
public Trigger leftStick(EventLoop loop) {
return button(XboxController.Button.kLeftStick.value, loop);
}
/**
* Constructs a Trigger instance around the right stick button's digital signal.
*
* @return a Trigger instance representing the right stick button's digital signal attached
* to the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
* @see #rightStick(EventLoop)
*/
public Trigger rightStick() {
return rightStick(getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the right stick button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return a Trigger instance representing the right stick button's digital signal attached
* to the given loop.
*/
public Trigger rightStick(EventLoop loop) {
return button(XboxController.Button.kRightStick.value, loop);
}
/**
* Constructs a Trigger instance around the axis value of the left trigger. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @param loop the event loop instance to attach the Trigger to.
* @return a Trigger instance that is true when the left trigger's axis exceeds the provided
* threshold, attached to the given event loop
*/
public Trigger leftTrigger(double threshold, EventLoop loop) {
return axisGreaterThan(XboxController.Axis.kLeftTrigger.value, threshold, loop);
}
/**
* Constructs a Trigger instance around the axis value of the left trigger. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @return a Trigger instance that is true when the left trigger's axis exceeds the provided
* threshold, attached to the {@link Scheduler#getDefaultEventLoop() default scheduler event
* loop} on the scheduler passed to the controller's constructor, or the {@link
* Scheduler#getDefault default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger leftTrigger(double threshold) {
return leftTrigger(threshold, getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the axis value of the left trigger. The returned trigger
* will be true when the axis value is greater than 0.5.
*
* @return a Trigger instance that is true when the left trigger's axis exceeds 0.5, attached to
* the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger leftTrigger() {
return leftTrigger(0.5);
}
/**
* Constructs a Trigger instance around the axis value of the right trigger. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @param loop the event loop instance to attach the Trigger to.
* @return a Trigger instance that is true when the right trigger's axis exceeds the provided
* threshold, attached to the given event loop
*/
public Trigger rightTrigger(double threshold, EventLoop loop) {
return axisGreaterThan(XboxController.Axis.kRightTrigger.value, threshold, loop);
}
/**
* Constructs a Trigger instance around the axis value of the right trigger. The returned
* trigger will be true when the axis value is greater than {@code threshold}.
*
* @param threshold the minimum axis value for the returned {@link Trigger} to be true. This value
* should be in the range [0, 1] where 0 is the unpressed state of the axis.
* @return a Trigger instance that is true when the right trigger's axis exceeds the provided
* threshold, attached to the {@link Scheduler#getDefaultEventLoop() default scheduler event
* loop} on the scheduler passed to the controller's constructor, or the {@link
* Scheduler#getDefault default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger rightTrigger(double threshold) {
return rightTrigger(threshold, getScheduler().getDefaultEventLoop());
}
/**
* Constructs a Trigger instance around the axis value of the right trigger. The returned trigger
* will be true when the axis value is greater than 0.5.
*
* @return a Trigger instance that is true when the right trigger's axis exceeds 0.5, attached to
* the {@link Scheduler#getDefaultEventLoop() default scheduler event loop} on the
* scheduler passed to the controller's constructor, or the {@link Scheduler#getDefault
* default scheduler} if a scheduler was not explicitly provided.
*/
public Trigger rightTrigger() {
return rightTrigger(0.5);
}
/**
* Get the X axis value of left side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getLeftX() {
return m_hid.getLeftX();
}
/**
* Get the X axis value of right side of the controller. Right is positive.
*
* @return The axis value.
*/
public double getRightX() {
return m_hid.getRightX();
}
/**
* Get the Y axis value of left side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getLeftY() {
return m_hid.getLeftY();
}
/**
* Get the Y axis value of right side of the controller. Back is positive.
*
* @return The axis value.
*/
public double getRightY() {
return m_hid.getRightY();
}
/**
* Get the left trigger axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getLeftTriggerAxis() {
return m_hid.getLeftTriggerAxis();
}
/**
* Get the right trigger axis value of the controller. Note that this axis is bound to the
* range of [0, 1] as opposed to the usual [-1, 1].
*
* @return The axis value.
*/
public double getRightTriggerAxis() {
return m_hid.getRightTriggerAxis();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import edu.wpi.first.util.ErrorMessages;
/**
* A single trigger binding.
*
* @param scope The scope in which the binding is active.
* @param type The type of binding; or, when the bound command should run
* @param command The bound command. Cannot be null.
* @param frames The stack frames when the binding was created. Used for telemetry and error
* reporting so if a command throws an exception, we can tell users where that command was bound
* instead of giving a fairly useless backtrace of the command framework.
*/
record Binding(BindingScope scope, BindingType type, Command command, StackTraceElement[] frames) {
public Binding {
ErrorMessages.requireNonNullParam(scope, "scope", "Binding");
ErrorMessages.requireNonNullParam(type, "type", "Binding");
ErrorMessages.requireNonNullParam(command, "command", "Binding");
ErrorMessages.requireNonNullParam(frames, "frames", "Binding");
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
/**
* A scope for when a binding is live. Bindings tied to a scope must be deleted when the scope
* becomes inactive.
*/
@SuppressWarnings("PMD.ImplicitFunctionalInterface")
interface BindingScope {
/**
* Checks if the scope is active. Bindings for inactive scopes are removed from the scheduler.
*
* @return True if the scope is still active, false if not.
*/
boolean active();
static BindingScope global() {
return Global.INSTANCE;
}
static BindingScope forCommand(Scheduler scheduler, Command command) {
return new ForCommand(scheduler, command);
}
/** A global binding scope. Bindings in this scope are always active. */
final class Global implements BindingScope {
// No reason not to be a singleton.
public static final Global INSTANCE = new Global();
@Override
public boolean active() {
return true;
}
}
/**
* A binding scoped to the lifetime of a specific command. This should be used when a binding is
* created within a command, tying the lifetime of the binding to the declaring command.
*
* @param scheduler The scheduler managing the command.
* @param command The command being scoped to.
*/
record ForCommand(Scheduler scheduler, Command command) implements BindingScope {
@Override
public boolean active() {
return scheduler.isRunning(command);
}
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
/** Describes when a command bound to a trigger should run. */
enum BindingType {
/**
* An immediate or manual binding created when calling {@link Scheduler#schedule(Command)}
* directly, without using a trigger to bind it to a signal.
*/
IMMEDIATE,
/**
* Schedules (forks) a command on a rising edge signal. The command will run until it completes or
* is interrupted by another command requiring the same mechanisms.
*/
SCHEDULE_ON_RISING_EDGE,
/**
* Schedules (forks) a command on a falling edge signal. The command will run until it completes
* or is interrupted by another command requiring the same mechanisms.
*/
SCHEDULE_ON_FALLING_EDGE,
/**
* Schedules (forks) a command on a rising edge signal. If the command is still running on the
* next rising edge, it will be canceled then; otherwise, it will be scheduled again.
*/
TOGGLE_ON_RISING_EDGE,
/**
* Schedules (forks) a command on a falling edge signal. If the command is still running on the
* next falling edge, it will be canceled then; otherwise, it will be scheduled again.
*/
TOGGLE_ON_FALLING_EDGE,
/**
* Schedules a command on a rising edge signal. If the command is still running on the next
* falling edge, it will be canceled then - unlike {@link #SCHEDULE_ON_RISING_EDGE}, which would
* allow it to continue to run.
*/
RUN_WHILE_HIGH,
/**
* Schedules a command on a falling edge signal. If the command is still running on the next
* rising edge, it will be canceled then - unlike {@link #SCHEDULE_ON_FALLING_EDGE}, which would
* allow it to continue to run.
*/
RUN_WHILE_LOW
}

View File

@@ -0,0 +1,416 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.units.measure.Time;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import org.wpilib.annotation.NoDiscard;
/**
* Performs some task using one or more {@link Mechanism mechanisms}. Commands are fundamentally
* backed by a {@link Coroutine} that can be used to write imperative code that runs asynchronously.
*
* <p>Programmers familiar with the earlier versions of the command framework can think of a v3
* command similar to a v1 or v2 command that executes the lifecycle methods in a single method, as
* demonstrated in the example below. (Note, however, that more sophisticated code than this is
* possible!
*
* <pre>{@code
* coroutine -> {
* initialize();
* while (!isFinished()) {
* execute();
* coroutine.yield(); // be sure to yield at the end of the loop
* }
* end();
* }
* }</pre>
*
* <p><strong>Note:</strong> Because coroutines are <i>opt-in</i> collaborate constructs, every
* command implementation <strong>must</strong> call {@link Coroutine#yield()} within any periodic
* loop. Failure to do so may result in an unrecoverable infinite loop.
*
* <h2>Requirements</h2>
*
* <p>Commands require zero or more mechanisms. To prevent conflicting control requests from running
* simultaneously (for example, commanding an elevator to both raise and lower at the same time), a
* running command has <i>exclusive ownership</i> of all of its required mechanisms. If another
* command with an equal or greater {@link #priority()} is scheduled that requires one or more of
* those same mechanisms, it will interrupt and cancel the running command.
*
* <p>The recommended way to create a command is using {@link Mechanism#run(Consumer)} or a related
* factory method to create commands that require a single mechanism (for example, a command that
* drives an elevator up and down or rotates an arm). Commands may be <i>composed</i> into {@link
* ParallelGroup parallel groups} and {@link SequentialGroup sequences} to build more complex
* behavior out of fundamental building blocks. These built-in compositions will require every
* mechanism used by every command in them, even if those commands aren't always running, and thus
* can leave certain required mechanisms in an <i>uncommanded</i> state: owned, but not used, this
* can lead to mechanisms sagging under gravity or running the previous motor control request they
* were given.
*
* <h2>Advanced Usage</h2>
*
* <p>For example, a hypothetical drive-and-score sequence could be coded in two ways: one with a
* sequence chain, or one that uses the lower-level coroutine API. Coroutines can be used in an
* async/await style that you may be familiar with from languages like JavaScript, Python, or C#
* (note that there is no {@code async} keyword; commands are inherently asynchronous). Nested
* commands may be forked and awaited, but will not outlive the command that forked them; there is
* no analog for something like a {@code ScheduleCommand} from the v2 commands framework.
*
* <pre>{@code
* class Robot extends TimedRobot {
* private final Drivetrain drivetrain = new Drivetrain();
* private final Elevator elevator = new Elevator();
* private final Gripper gripper = new Gripper();
*
* // Basic sequence builder - owns all 3 mechanisms for the full duration,
* // even when they're not being used
* private Command basicScoringSequence() {
* return drivetrain.driveToScoringLocation()
* .andThen(elevator.moveToScoringHeight())
* .andThen(gripper.release())
* .named("Scoring Sequence (Basic)");
* }
*
* // Advanced sequence with async/await - only owns mechanisms while they're
* // being used, allowing default commands or other user-triggered commands
* // to run when not in use. Interrupting one of the inner commands while it's
* // running will cancel the entire sequence.
* private Command advancedScoringSequence() {
* return Command.noRequirements().executing(coroutine -> {
* coroutine.await(drivetrain.driveToScoringLocation());
* coroutine.await(elevator.moveToScoringHeight());
* coroutine.await(gripper.release());
* }).named("Scoring Sequence (Advanced)");
* }
* }
* }</pre>
*/
@NoDiscard("Commands must be used! Did you mean to fork it or bind it to a trigger?")
public interface Command {
/** The default command priority. */
int DEFAULT_PRIORITY = 0;
/**
* The lowest possible command priority. Commands with the lowest priority can be interrupted by
* any other command, including other minimum-priority commands.
*/
int LOWEST_PRIORITY = Integer.MIN_VALUE;
/**
* The highest possible command priority. Commands with the highest priority can only be
* interrupted by other maximum-priority commands.
*/
int HIGHEST_PRIORITY = Integer.MAX_VALUE;
/**
* Runs the command. Commands that need to periodically run until a goal state is reached should
* simply run a while loop like {@code while (!atGoal()) { ... } } and call {@link
* Coroutine#yield()} at the end of the loop.
*
* <p><strong>Warning:</strong> any loops in a command must call {@code coroutine.yield()}.
* Failure to do so will prevent anything else in your robot code from running. Commands are
* <i>opt-in</i> collaborative constructs; don't be greedy!
*
* @param coroutine the coroutine backing the command's execution
*/
void run(Coroutine coroutine);
/**
* An optional lifecycle hook that can be implemented to execute some code whenever this command
* is canceled before it naturally completes. Commands should be careful to do a single-shot
* cleanup (for example, setting a motor to zero volts) and not do any complex looping logic here.
*/
default void onCancel() {
// NOP by default
}
/**
* The name of the command.
*
* @return the name of the command
*/
@NoDiscard
String name();
/**
* The mechanisms required by the command. This is used by the scheduler to determine if two
* commands conflict with each other. No mechanism may be required by more than one running
* command at a time.
*
* @return the set of mechanisms required by the command
*/
@NoDiscard
Set<Mechanism> requirements();
/**
* The priority of the command. If a command is scheduled that conflicts with another running or
* pending command, their priority values are compared to determine which should run. If the
* scheduled command is lower priority than the running command, then it will not be scheduled and
* the running command will continue to run. If it is the same or higher priority, then the
* running command will be canceled and the scheduled command will start to run.
*
* @return the priority of the command
*/
@NoDiscard
default int priority() {
return DEFAULT_PRIORITY;
}
/**
* Checks if this command has a lower {@link #priority() priority} than another command.
*
* @param other the command to compare with
* @return true if this command has a lower priority than the other one, false otherwise
*/
@NoDiscard
default boolean isLowerPriorityThan(Command other) {
requireNonNullParam(other, "other", "Command.isLowerPriorityThan");
return priority() < other.priority();
}
/**
* Checks if this command requires a particular mechanism.
*
* @param mechanism the mechanism to check
* @return true if the mechanism is required, false if not
*/
@NoDiscard
default boolean requires(Mechanism mechanism) {
return requirements().contains(mechanism);
}
/**
* Checks if this command conflicts with another command.
*
* @param other the commands to check against
* @return true if both commands require at least one of the same mechanism, false if both
* commands have completely different requirements
*/
@NoDiscard
default boolean conflictsWith(Command other) {
requireNonNullParam(other, "other", "Command.conflictsWith");
return !Collections.disjoint(requirements(), other.requirements());
}
/**
* Creates a new command that runs this one for a maximum duration, after which it is forcibly
* canceled. This is particularly useful for autonomous routines where you want to prevent your
* entire autonomous period spent stuck on a single action because a mechanism doesn't quite reach
* its setpoint (for example, spinning up a flywheel or driving to a particular location on the
* field). The resulting command will have the same name as this one, with the timeout period
* appended.
*
* @param timeout the maximum duration that the command is permitted to run. Negative or zero
* values will result in the command running only once before being canceled.
* @return the timed out command.
*/
default Command withTimeout(Time timeout) {
requireNonNullParam(timeout, "timeout", "Command.withTimeout");
return race(this, waitFor(timeout).named("Timeout: " + timeout.toLongString()))
.named(name() + " [" + timeout.toLongString() + " timeout]");
}
/**
* Creates a command that does not require any hardware; that is, it does not affect the state of
* any physical objects. This is useful for commands that do some cleanup or state management,
* such as resetting odometry or sensors, that you don't want to interrupt a command that's
* controlling the mechanisms it affects.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @return a builder that can be used to configure the resulting command
*/
static NeedsExecutionBuilderStage noRequirements() {
return new StagedCommandBuilder().noRequirements();
}
/**
* Creates a command that requires one or more mechanisms.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param requirement The first required mechanism
* @param rest Any other required mechanisms
* @return A command builder
*/
static NeedsExecutionBuilderStage requiring(Mechanism requirement, Mechanism... rest) {
// parameters will be null checked by the builder
return new StagedCommandBuilder().requiring(requirement, rest);
}
/**
* Creates command that requires some number of mechanisms.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param requirements The required mechanisms. May be empty, but cannot contain null values.
* @return A command builder
*/
static NeedsExecutionBuilderStage requiring(Collection<Mechanism> requirements) {
// parameters will be null checked by the builder
return new StagedCommandBuilder().requiring(requirements);
}
/**
* Starts creating a command that runs a group of multiple commands in parallel. The command will
* complete when every command in the group has completed naturally.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param commands The commands to run in parallel
* @return A command builder
*/
static ParallelGroupBuilder parallel(Command... commands) {
// parameters will be null checked by the builder
return new ParallelGroupBuilder().requiring(commands);
}
/**
* Starts creating a command that runs a group of multiple commands in parallel. The command will
* complete when any command in the group has completed naturally; all other commands in the group
* will be canceled.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param commands The commands to run in parallel
* @return A command builder
*/
static ParallelGroupBuilder race(Command... commands) {
// parameters will be null checked by the builder
return new ParallelGroupBuilder().optional(commands);
}
/**
* Starts creating a command that runs a group of multiple commands in sequence. The command will
* complete when the last command in the group has completed naturally. Commands in the group will
* run in the order they're passed to this method.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link SequentialGroupBuilder} for details.
*
* @param commands The commands to run in sequence.
* @return A command builder
*/
static SequentialGroupBuilder sequence(Command... commands) {
// parameters will be null checked by the builder
return new SequentialGroupBuilder().andThen(commands);
}
/**
* Starts creating a command that simply waits for some condition to be met. The command will not
* require any mechanisms.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link StagedCommandBuilder} for details.
*
* @param condition The condition to wait for
* @return A command builder
*/
static NeedsNameBuilderStage waitUntil(BooleanSupplier condition) {
requireNonNullParam(condition, "condition", "Command.waitUntil");
return noRequirements().executing(coroutine -> coroutine.waitUntil(condition));
}
/**
* Creates a command that simply waits for a given duration. The command will not require any
* mechanisms.
*
* @param duration How long to wait
* @return A command builder
*/
static NeedsNameBuilderStage waitFor(Time duration) {
requireNonNullParam(duration, "duration", "Command.waitFor");
return noRequirements().executing(coroutine -> coroutine.wait(duration));
}
/**
* Creates a command that runs this one and ends when the end condition is met (if this command
* has not already exited by then).
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param endCondition The end condition to wait for.
* @return The waiting command
*/
default ParallelGroupBuilder until(BooleanSupplier endCondition) {
requireNonNullParam(endCondition, "endCondition", "Command.until");
return new ParallelGroupBuilder()
.optional(this, Command.waitUntil(endCondition).named("Until Condition"));
}
/**
* Creates a command sequence, starting from this command and then running the next one. More
* commands can be added with the builder before naming and creating the sequence.
*
* <pre>{@code
* Sequence aThenBThenC =
* commandA()
* .andThen(commandB())
* .andThen(commandC())
* .withAutomaticName();
* }</pre>
*
* @param next The command to run after this one in the sequence
* @return A sequence builder
*/
default SequentialGroupBuilder andThen(Command next) {
// parameter will be null checked by the builder
return new SequentialGroupBuilder().andThen(this).andThen(next);
}
/**
* Creates a parallel command group, running this command alongside one or more other commands.
* The group will exit once every command has finished.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* <pre>{@code
* ParallelGroup abc =
* commandA()
* .alongWith(commandB(), commandC())
* .withAutomaticName();
* }</pre>
*
* @param parallel The commands to run in parallel with this one
* @return A parallel group builder
*/
default ParallelGroupBuilder alongWith(Command... parallel) {
return new ParallelGroupBuilder().requiring(this).requiring(parallel);
}
/**
* Creates a parallel command group, running this command alongside one or more other commands.
* The group will exit after any command finishes.
*
* <p>More configuration options are needed after calling this function before the command can be
* created. See {@link ParallelGroupBuilder} for details.
*
* @param parallel The commands to run in parallel with this one
* @return A parallel group builder
*/
default ParallelGroupBuilder raceWith(Command... parallel) {
return new ParallelGroupBuilder().optional(this).optional(parallel);
}
}

View File

@@ -0,0 +1,100 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static java.util.Objects.requireNonNull;
/** Represents the state of a command as it is executed by the scheduler. */
final class CommandState {
// Two billion unique IDs should be enough for anybody.
private static int s_lastId = 0;
private final Command m_command;
private final Command m_parent;
private final Coroutine m_coroutine;
private final Binding m_binding;
private double m_lastRuntimeMs = -1;
private double m_totalRuntimeMs;
private final int m_id;
/**
* Creates a new command state object.
*
* @param command The command being tracked.
* @param parent The parent command composition that scheduled the tracked command. Null if the
* command was scheduled by a top level construct like trigger bindings or manually scheduled
* by a user. For deeply nested compositions, this tracks the <i>direct parent command</i>
* that invoked the schedule() call; in this manner, an ancestry tree can be built, where each
* {@code CommandState} object references a parent node in the tree.
* @param coroutine The coroutine to which the command is bound.
* @param binding The binding that caused the command to be scheduled.
*/
CommandState(Command command, Command parent, Coroutine coroutine, Binding binding) {
m_command = requireNonNull(command, "WPILib bug: command state given null command");
m_parent = parent; // allowed to be null
m_coroutine = requireNonNull(coroutine, "WPILib bug: command state given null coroutine");
m_binding = requireNonNull(binding, "WPILib bug: command state given null binding");
// This is incredibly non-thread safe, but nobody should be using the command framework across
// multiple threads anyway. Worst case scenario, we'll get duplicate IDs for commands scheduled
// by different threads and cause bad telemetry data without affecting program correctness.
m_id = ++s_lastId;
}
public Command command() {
return m_command;
}
public Command parent() {
return m_parent;
}
public Coroutine coroutine() {
return m_coroutine;
}
public Binding binding() {
return m_binding;
}
/**
* How long the command took to run the last time it executed. Defaults to -1 if the command has
* not run at least once.
*
* @return The runtime, in milliseconds.
*/
public double lastRuntimeMs() {
return m_lastRuntimeMs;
}
public void setLastRuntimeMs(double lastRuntimeMs) {
m_lastRuntimeMs = lastRuntimeMs;
m_totalRuntimeMs += lastRuntimeMs;
}
public double totalRuntimeMs() {
return m_totalRuntimeMs;
}
public int id() {
return m_id;
}
@Override
public boolean equals(Object obj) {
return obj instanceof CommandState that && m_id == that.m_id;
}
@Override
public int hashCode() {
return m_id;
}
@Override
public String toString() {
return "CommandState[command=%s, parent=%s, coroutine=%s, id=%d]"
.formatted(m_command, m_parent, m_coroutine, m_id);
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
final class CommandTraceHelper {
private CommandTraceHelper() {
// Utility class
}
/**
* Creates a modified stack trace where the trace of the scheduling code replaces the trace of the
* internal scheduler logic.
*
* @param commandExceptionTrace The trace of the exception raised during command execution.
* @param commandScheduleTrace The trace of when the command was scheduled.
* @return A new array of stack trace elements.
*/
public static StackTraceElement[] modifyTrace(
StackTraceElement[] commandExceptionTrace, StackTraceElement[] commandScheduleTrace) {
List<StackTraceElement> frames = new ArrayList<>();
List<String> filteredClasses =
List.of(
Coroutine.class.getName(),
Continuation.class.getName(),
Scheduler.class.getName(),
"org.wpilib.commands3.StagedCommandBuilder$BuilderBackedCommand",
"jdk.internal.vm.Continuation");
boolean sawRun = false;
for (var exceptionFrame : commandExceptionTrace) {
if (!filteredClasses.contains(exceptionFrame.getClassName())) {
frames.add(exceptionFrame);
}
// Inject the schedule trace immediately after the line of user code that called Scheduler.run
if (sawRun) {
// Inject a marker frame just so there's a distinction between the command's code and the
// code that scheduled it, since they occur asynchronously
frames.add(new StackTraceElement("=== Command Binding Trace ===", "", "", -1));
// Drop internal trigger frames, since they're not helpful for users.
// The first frame here should be the line of user code that bound the command to a trigger.
Stream.of(commandScheduleTrace)
.dropWhile(
frame -> {
boolean inTriggerInternals = frame.getClassName().equals(Trigger.class.getName());
boolean isScheduleCall =
frame.getClassName().equals(Scheduler.class.getName())
&& "schedule".equals(frame.getMethodName());
return inTriggerInternals || isScheduleCall;
})
.filter(frame -> !filteredClasses.contains(frame.getClassName()))
.forEach(frames::add);
break;
}
if (exceptionFrame.getClassName().equals(Scheduler.class.getName())
&& "run".equals(exceptionFrame.getMethodName())) {
sawRun = true;
}
}
return frames.toArray(StackTraceElement[]::new);
}
}

View File

@@ -0,0 +1,122 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/** Utility class for helping with detecting conflicts between commands. */
public final class ConflictDetector {
private ConflictDetector() {
// This is a utility class!
}
/**
* A conflict between two commands.
*
* @param a The first conflicting command.
* @param b The second conflicting command.
* @param sharedRequirements The set of mechanisms required by both commands. This set is
* read-only
*/
public record Conflict(Command a, Command b, Set<Mechanism> sharedRequirements) {
/**
* Gets a descriptive message for the conflict. The description includes the names of the
* conflicting commands and the names of all mechanisms required by both commands.
*
* @return A description of the conflict.
*/
public String description() {
var shared =
sharedRequirements.stream()
.map(Mechanism::getName)
.sorted()
.collect(Collectors.joining(", "));
return "%s and %s both require %s".formatted(a.name(), b.name(), shared);
}
}
/**
* Validates that a set of commands have no internal requirement conflicts. An error is thrown if
* a conflict is detected.
*
* @param commands The commands to validate
* @throws IllegalArgumentException If at least one pair of commands is found in the input where
* both commands have at least one required mechanism in common
*/
public static void throwIfConflicts(Collection<? extends Command> commands) {
requireNonNullParam(commands, "commands", "ConflictDetector.throwIfConflicts");
var conflicts = findAllConflicts(commands);
if (conflicts.isEmpty()) {
// No conflicts, all good
return;
}
StringBuilder message =
new StringBuilder("Commands running in parallel cannot share requirements: ");
for (int i = 0; i < conflicts.size(); i++) {
Conflict conflict = conflicts.get(i);
message.append(conflict.description());
if (i < conflicts.size() - 1) {
message.append("; ");
}
}
throw new IllegalArgumentException(message.toString());
}
/**
* Finds all conflicting pairs of commands in the input collection.
*
* @param commands The commands to check.
* @return All detected conflicts. The returned list is empty if no conflicts were found.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public static List<Conflict> findAllConflicts(Collection<? extends Command> commands) {
requireNonNullParam(commands, "commands", "ConflictDetector.findAllConflicts");
List<Conflict> conflicts = new ArrayList<>();
int i = 0;
for (Command command : commands) {
i++;
int j = 0;
for (Command other : commands) {
j++;
if (j <= i) {
// Skip all elements in 0..i so the inner loop only checks elements from i + 1 onward.
// Ordering of the elements in the pair doesn't matter, and this prevents finding every
// pair twice eg (A, B) and (B, A).
continue;
}
if (command == other) {
// Commands cannot conflict with themselves, so just in case the input collection happens
// to have duplicate elements we just skip any pairs of a command with itself.
continue;
}
if (command.conflictsWith(other)) {
conflicts.add(findConflict(command, other));
}
}
}
return conflicts;
}
private static Conflict findConflict(Command a, Command b) {
Set<Mechanism> sharedRequirements = new HashSet<>(a.requirements());
sharedRequirements.retainAll(b.requirements());
return new Conflict(a, b, Set.copyOf(sharedRequirements));
}
}

View File

@@ -0,0 +1,230 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
/**
* A wrapper around the JDK internal Continuation class. Continuations are one-shot (i.e., not
* reusable after completion) and allow stack frames to be paused and resumed at a later time. They
* are the underpinning for virtual threads, which have their own scheduler and JVM support. Bare
* continuations are designed for internal use by the JVM; we have forcibly opened access to them
* for use by the commands framework due to limitations of virtual threads; notably, their complete
* lack of determinism and timing, which are critically important for real-time systems like robots.
*
* <p><strong>ONLY USE CONTINUATIONS IN A SINGLE THREADED CONTEXT.</strong> The JVM and JIT
* compilers make fundamental assumptions about how continuations are used, and rely on the code
* that uses it (which was intended to be virtual threads) to use it safely. <strong>Failure to use
* this API safely can result in JIT compilers to issue invalid code causing buggy behavior and JVM
* crashes at any time, up to and including on a field during an official match.</strong>
*
* <p>Teams don't need to use continuations directly with the commands framework; all mounting and
* unmounting is handled by the command scheduler and a coroutine wrapper.
*/
final class Continuation {
// The underlying jdk.internal.vm.Continuation object
// https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/java.base/share/classes/jdk/internal/vm/Continuation.java
private final Object m_continuation;
static final Class<?> jdk_internal_vm_Continuation;
private static final MethodHandle CONSTRUCTOR;
@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
private static final MethodHandle YIELD;
@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
private static final MethodHandle RUN;
private static final MethodHandle IS_DONE;
private static final MethodHandle java_lang_thread_setContinuation;
private static final ThreadLocal<Continuation> mountedContinuation = new ThreadLocal<>();
static {
try {
jdk_internal_vm_Continuation = Class.forName("jdk.internal.vm.Continuation");
var lookup =
MethodHandles.privateLookupIn(jdk_internal_vm_Continuation, MethodHandles.lookup());
CONSTRUCTOR =
lookup.findConstructor(
jdk_internal_vm_Continuation,
MethodType.methodType(
void.class, ContinuationScope.jdk_internal_vm_ContinuationScope, Runnable.class));
YIELD =
lookup.findStatic(
jdk_internal_vm_Continuation,
"yield",
MethodType.methodType(
boolean.class, ContinuationScope.jdk_internal_vm_ContinuationScope));
RUN =
lookup.findVirtual(
jdk_internal_vm_Continuation, "run", MethodType.methodType(void.class));
IS_DONE =
lookup.findVirtual(
jdk_internal_vm_Continuation, "isDone", MethodType.methodType(boolean.class));
} catch (Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
static {
try {
var lookup = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup());
java_lang_thread_setContinuation =
lookup.findVirtual(
Thread.class,
"setContinuation",
MethodType.methodType(void.class, Continuation.jdk_internal_vm_Continuation));
} catch (Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
/**
* Used to wrap any checked exceptions bubbled from when calling the internal continuation methods
* via reflection. Per the Continuation API as of Java 21, none of the methods we're calling will
* throw unchecked exceptions (IllegalState or other runtime exceptions, yes, and we bubble those
* up directly); however, the MethodHandle API's `invoke` method has `throws Throwable` in its
* signature and we have to handle it.
*/
@SuppressWarnings("PMD.DoNotExtendJavaLangError")
static final class InternalContinuationError extends Error {
InternalContinuationError(String message, Throwable cause) {
super(message, cause);
}
}
private final ContinuationScope m_scope;
/**
* Constructs a continuation.
*
* @param scope the continuation's scope, used in yield
* @param target the continuation's body
*/
@SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException"})
Continuation(ContinuationScope scope, Runnable target) {
try {
m_continuation = CONSTRUCTOR.invoke(scope.m_continuationScope, target);
m_scope = scope;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
/**
* Suspends the current continuations up to this continuation's scope.
*
* @return {@code true} for success; {@code false} for failure
* @throws IllegalStateException if not currently in this continuation's scope
*/
@SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException"})
public boolean yield() {
try {
return (boolean) YIELD.invoke(m_scope.m_continuationScope);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new InternalContinuationError(
"Continuation.yield() encountered an unexpected error", t);
}
}
/**
* Mounts and runs the continuation body until the body calls {@link #yield()}. If the
* continuation is suspended, it will continue from the most recent yield point.
*/
@SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException"})
public void run() {
try {
RUN.invoke(m_continuation);
} catch (WrongMethodTypeException | ClassCastException e) {
throw new IllegalStateException("Unable to run the underlying continuation!", e);
} catch (RuntimeException | Error e) {
// The bound task threw an exception; re-throw it
throw e;
} catch (Throwable t) {
throw new InternalContinuationError("Continuation.run() encountered an unexpected error", t);
}
}
/**
* Tests whether this continuation is completed.
*
* @return whether this continuation is completed
*/
@SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException"})
public boolean isDone() {
try {
return (boolean) IS_DONE.invoke(m_continuation);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new InternalContinuationError(
"Continuation.isDone() encountered an unexpected error", t);
}
}
/**
* Mounds a continuation to the current thread. Accepts null for clearing the currently mounted
* continuation.
*
* @param continuation the continuation to mount
*/
@SuppressWarnings({"PMD.AvoidRethrowingException", "PMD.AvoidCatchingGenericException"})
public static void mountContinuation(Continuation continuation) {
try {
if (continuation == null) {
java_lang_thread_setContinuation.invoke(Thread.currentThread(), null);
mountedContinuation.remove();
} else {
java_lang_thread_setContinuation.invoke(
Thread.currentThread(), continuation.m_continuation);
mountedContinuation.set(continuation);
}
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
// `t` is anything thrown internally by Thread.setContinuation.
// It only assigns to a field, no way to throw
// However, if the invocation fails for some reason, we'll end up with an
// IllegalStateException when attempting to run an unmounted continuation
throw new InternalContinuationError(
"Continuation.mountContinuation() encountered an unexpected error", t);
}
}
/**
* Gets the currently mounted continuation. This is thread-local value; calling this method on two
* different threads will give two different results.
*
* @return The continuation mounted on the current thread.
*/
public static Continuation getMountedContinuation() {
return mountedContinuation.get();
}
@Override
public String toString() {
return m_continuation.toString();
}
@SuppressWarnings("PMD.CompareObjectsWithEquals")
boolean isMounted() {
return this == getMountedContinuation();
}
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
/** A continuation scope. */
final class ContinuationScope {
// The underlying jdk.internal.vm.ContinuationScope object
// https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/java.base/share/classes/jdk/internal/vm/ContinuationScope.java
final Object m_continuationScope;
static final Class<?> jdk_internal_vm_ContinuationScope;
private static final MethodHandle CONSTRUCTOR;
static {
try {
jdk_internal_vm_ContinuationScope = Class.forName("jdk.internal.vm.ContinuationScope");
var lookup =
MethodHandles.privateLookupIn(jdk_internal_vm_ContinuationScope, MethodHandles.lookup());
CONSTRUCTOR =
lookup.findConstructor(
jdk_internal_vm_ContinuationScope, MethodType.methodType(void.class, String.class));
} catch (Throwable t) {
throw new ExceptionInInitializerError(t);
}
}
/**
* Constructs a new scope.
*
* @param name The scope's name
*/
ContinuationScope(String name) {
Objects.requireNonNull(name);
try {
m_continuationScope = CONSTRUCTOR.invoke(name);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return m_continuationScope.toString();
}
}

View File

@@ -0,0 +1,377 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.units.Units.Seconds;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.units.measure.Time;
import edu.wpi.first.wpilibj.Timer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
/**
* A coroutine object is injected into command's {@link Command#run(Coroutine)} method to allow
* commands to yield and compositions to run other commands. Commands are considered <i>bound</i> to
* a coroutine while they're scheduled; attempting to use a coroutine outside the command bound to
* it will result in an {@code IllegalStateException} being thrown.
*/
public final class Coroutine {
private final Scheduler m_scheduler;
private final Continuation m_backingContinuation;
/**
* Creates a new coroutine. Package-private; only the scheduler should be creating these.
*
* @param scheduler The scheduler running the coroutine
* @param scope The continuation scope the coroutine's backing continuation runs in
* @param callback The callback for the continuation to execute when mounted. Often a command
* function's body.
*/
Coroutine(Scheduler scheduler, ContinuationScope scope, Consumer<Coroutine> callback) {
m_scheduler = scheduler;
m_backingContinuation = new Continuation(scope, () -> callback.accept(this));
}
/**
* Yields control back to the scheduler to allow other commands to execute. This can be thought of
* as "pausing" the currently executing command.
*
* @return true
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public boolean yield() {
requireMounted();
try {
return m_backingContinuation.yield();
} catch (IllegalStateException e) {
if ("Pinned: MONITOR".equals(e.getMessage())) {
// Raised when a continuation yields inside a synchronized block or method:
// https://github.com/openjdk/jdk/blob/jdk-21%2B35/src/java.base/share/classes/jdk/internal/vm/Continuation.java#L396-L402
// Note: Not a thing in Java 24+
// Rethrow with an error message that's more helpful for our users
throw new IllegalStateException(
"Coroutine.yield() cannot be called inside a synchronized block or method. "
+ "Consider using a Lock instead of synchronized, "
+ "or rewrite your code to avoid locks and mutexes altogether.",
e);
} else {
// rethrow
throw e;
}
}
}
/**
* Parks the current command. No code in a command declared after calling {@code park()} will be
* executed. A parked command will never complete naturally and must be interrupted or canceled.
*
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
@SuppressWarnings("InfiniteLoopStatement")
public void park() {
requireMounted();
while (true) {
// 'this' is required because 'yield' is a semi-keyword and needs to be qualified
this.yield();
}
}
/**
* Schedules a child command and then immediately returns. The child command will run until its
* natural completion, the parent command exits, or the parent command cancels it.
*
* <p>This is a nonblocking operation. To fork and then wait for the child command to complete,
* use {@link #await(Command)}.
*
* <p>The parent command will continue executing while the child command runs, and can resync with
* the child command using {@link #await(Command)}.
*
* <pre>{@code
* Command example() {
* return Command.noRequirements().executing(coroutine -> {
* Command child = ...;
* coroutine.fork(child);
* // ... do more things
* // then sync back up with the child command
* coroutine.await(child);
* }).named("Example");
* }
* }</pre>
*
* <p>Note: forking a command that conflicts with a higher-priority command will fail. The forked
* command will not be scheduled, and the existing command will continue to run.
*
* @param commands The commands to fork.
* @throws IllegalStateException if called anywhere other than the coroutine's running command
* @see #await(Command)
*/
public void fork(Command... commands) {
requireMounted();
requireNonNullParam(commands, "commands", "Coroutine.fork");
for (int i = 0; i < commands.length; i++) {
requireNonNullParam(commands[i], "commands[" + i + "]", "Coroutine.fork");
}
// Check for user error; there's no reason to fork conflicting commands simultaneously
ConflictDetector.throwIfConflicts(List.of(commands));
// Shorthand; this is handy for user-defined compositions
for (var command : commands) {
m_scheduler.schedule(command);
}
}
/**
* Forks off some commands. Each command will run until its natural completion, the parent command
* exits, or the parent command cancels it. The parent command will continue executing while the
* forked commands run, and can resync with the forked commands using {@link
* #awaitAll(Collection)}.
*
* <pre>{@code
* Command example() {
* return Command.noRequirements().executing(coroutine -> {
* Collection<Command> innerCommands = ...;
* coroutine.fork(innerCommands);
* // ... do more things
* // then sync back up with the inner commands
* coroutine.awaitAll(innerCommands);
* }).named("Example");
* }
* }</pre>
*
* <p>Note: forking a command that conflicts with a higher-priority command will fail. The forked
* command will not be scheduled, and the existing command will continue to run.
*
* @param commands The commands to fork.
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void fork(Collection<? extends Command> commands) {
fork(commands.toArray(Command[]::new));
}
/**
* Awaits completion of a command. If the command is not currently scheduled or running, it will
* be scheduled automatically. This is a blocking operation and will not return until the command
* completes or has been interrupted by another command scheduled by the same parent.
*
* @param command the command to await
* @throws IllegalStateException if called anywhere other than the coroutine's running command
* @see #fork(Command...)
*/
public void await(Command command) {
requireMounted();
requireNonNullParam(command, "command", "Coroutine.await");
m_scheduler.schedule(command);
while (m_scheduler.isScheduledOrRunning(command)) {
// If the command is a one-shot, then the schedule call will completely execute the command.
// There would be nothing to await
this.yield();
}
}
/**
* Awaits completion of all given commands. If any command is not current scheduled or running, it
* will be scheduled.
*
* @param commands the commands to await
* @throws IllegalArgumentException if any of the commands conflict with each other
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void awaitAll(Collection<? extends Command> commands) {
requireMounted();
requireNonNullParam(commands, "commands", "Coroutine.awaitAll");
int i = 0;
for (Command command : commands) {
requireNonNullParam(command, "commands[" + i + "]", "Coroutine.awaitAll");
i++;
}
ConflictDetector.throwIfConflicts(commands);
for (var command : commands) {
m_scheduler.schedule(command);
}
while (commands.stream().anyMatch(m_scheduler::isScheduledOrRunning)) {
this.yield();
}
}
/**
* Awaits completion of all given commands. If any command is not current scheduled or running, it
* will be scheduled.
*
* @param commands the commands to await
* @throws IllegalArgumentException if any of the commands conflict with each other
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void awaitAll(Command... commands) {
awaitAll(Arrays.asList(commands));
}
/**
* Awaits completion of any given commands. Any command that's not already scheduled or running
* will be scheduled. After any of the given commands completes, the rest will be canceled.
*
* @param commands the commands to await
* @throws IllegalArgumentException if any of the commands conflict with each other
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void awaitAny(Collection<? extends Command> commands) {
requireMounted();
requireNonNullParam(commands, "commands", "Coroutine.awaitAny");
int i = 0;
for (Command command : commands) {
requireNonNullParam(command, "commands[" + i + "]", "Coroutine.awaitAny");
i++;
}
ConflictDetector.throwIfConflicts(commands);
// Schedule anything that's not already queued or running
for (var command : commands) {
m_scheduler.schedule(command);
}
while (commands.stream().allMatch(m_scheduler::isScheduledOrRunning)) {
this.yield();
}
// At least one command exited; cancel the rest.
commands.forEach(m_scheduler::cancel);
}
/**
* Awaits completion of any given commands. Any command that's not already scheduled or running
* will be scheduled. After any of the given commands completes, the rest will be canceled.
*
* @param commands the commands to await
* @throws IllegalArgumentException if any of the commands conflict with each other
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void awaitAny(Command... commands) {
awaitAny(Arrays.asList(commands));
}
/**
* Waits for some duration of time to elapse. Returns immediately if the given duration is zero or
* negative. Call this within a command or command composition to introduce a simple delay.
*
* <p>For example, a basic autonomous routine that drives straight for 5 seconds:
*
* <pre>{@code
* Command timedDrive() {
* return drivebase.run(coroutine -> {
* drivebase.tankDrive(1, 1);
* coroutine.wait(Seconds.of(5));
* drivebase.stop();
* }).named("Timed Drive");
* }
* }</pre>
*
* <p>Note that the resolution of the wait period is equal to the period at which {@link
* Scheduler#run()} is called by the robot program. If using a 20 millisecond update period, the
* wait will be rounded up to the nearest 20 millisecond interval; in this scenario, calling
* {@code wait(Milliseconds.of(1))} and {@code wait(Milliseconds.of(19))} would have identical
* effects.
*
* <p>Very small loop times near the loop period are sensitive to the order in which commands are
* executed. If a command waits for 10 ms at the end of a scheduler cycle, and then all the
* commands that ran before it complete or exit, and then the next cycle starts immediately, the
* wait will be evaluated at the <i>start</i> of that next cycle, which occurred less than 10 ms
* later. Therefore, the wait will see not enough time has passed and only exit after an
* additional cycle elapses, adding an unexpected extra 20 ms to the wait time. This becomes less
* of a problem with smaller loop periods, as the extra 1-loop delay becomes smaller.
*
* @param duration the duration of time to wait
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void wait(Time duration) {
requireMounted();
requireNonNullParam(duration, "duration", "Coroutine.wait");
var timer = new Timer();
timer.start();
while (!timer.hasElapsed(duration.in(Seconds))) {
this.yield();
}
}
/**
* Yields until a condition is met.
*
* @param condition The condition to wait for
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public void waitUntil(BooleanSupplier condition) {
requireMounted();
requireNonNullParam(condition, "condition", "Coroutine.waitUntil");
while (!condition.getAsBoolean()) {
this.yield();
}
}
/**
* Advanced users only: this permits access to the backing command scheduler to run custom logic
* not provided by the standard coroutine methods. Any commands manually scheduled through this
* will be canceled when the parent command exits - it's not possible to "fork" a command that
* lives longer than the command that scheduled it.
*
* @return the command scheduler backing this coroutine
* @throws IllegalStateException if called anywhere other than the coroutine's running command
*/
public Scheduler scheduler() {
requireMounted();
return m_scheduler;
}
private boolean isMounted() {
return m_backingContinuation.isMounted();
}
private void requireMounted() {
// Note: attempting to yield() outside a command will already throw an error due to the
// continuation being unmounted, but other actions like forking and awaiting should also
// throw errors. For consistent messaging, we use this helper in all places, not just the
// ones that interact with the backing continuation.
if (isMounted()) {
return;
}
throw new IllegalStateException("Coroutines can only be used by the command bound to them");
}
// Package-private for interaction with the scheduler.
// These functions are not intended for team use.
void runToYieldPoint() {
m_backingContinuation.run();
}
void mount() {
Continuation.mountContinuation(m_backingContinuation);
}
boolean isDone() {
return m_backingContinuation.isDone();
}
}

View File

@@ -0,0 +1,165 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import edu.wpi.first.units.measure.Time;
import java.util.List;
import java.util.function.Consumer;
import org.wpilib.annotation.NoDiscard;
/**
* Generic base class to represent mechanisms on a robot. Commands can require sole ownership of a
* mechanism; when a command that requires a mechanism is running, no other commands may use it at
* the same time.
*
* <p>Even though this class is named "Mechanism", it may be used to represent other physical
* hardware on a robot that should be controlled with commands - for example, an LED strip or a
* vision processor that can switch between different pipelines could be represented as mechanisms.
*/
public class Mechanism {
private final String m_name;
private final Scheduler m_registeredScheduler;
/**
* Creates a new mechanism registered with the default scheduler instance and named using the name
* of the class. Intended to be used by subclasses to get sane defaults without needing to
* manually declare a constructor.
*/
@SuppressWarnings("this-escape")
protected Mechanism() {
m_name = getClass().getSimpleName();
m_registeredScheduler = Scheduler.getDefault();
setDefaultCommand(idle());
}
/**
* Creates a new mechanism, registered with the default scheduler instance.
*
* @param name The name of the mechanism. Cannot be null.
*/
public Mechanism(String name) {
this(name, Scheduler.getDefault());
}
/**
* Creates a new mechanism, registered with the given scheduler instance.
*
* @param name The name of the mechanism. Cannot be null.
* @param scheduler The registered scheduler. Cannot be null.
*/
@SuppressWarnings("this-escape")
public Mechanism(String name, Scheduler scheduler) {
m_name = name;
m_registeredScheduler = scheduler;
setDefaultCommand(idle());
}
/**
* Gets the name of this mechanism.
*
* @return The name of the mechanism.
*/
@NoDiscard
public String getName() {
return m_name;
}
/**
* Sets the default command to run on the mechanism when no other command is scheduled. The
* default command's priority is effectively the minimum allowable priority for any command
* requiring a mechanism. For this reason, it's recommended that a default command have a priority
* less than {@link Command#DEFAULT_PRIORITY} so it doesn't prevent low-priority commands from
* running.
*
* <p>The default command is initially an idle command that only owns the mechanism without doing
* anything. This command has the lowest possible priority to allow any other command to run.
*
* @param defaultCommand the new default command
*/
public void setDefaultCommand(Command defaultCommand) {
m_registeredScheduler.setDefaultCommand(this, defaultCommand);
}
/**
* Gets the default command that was set by the latest call to {@link
* #setDefaultCommand(Command)}.
*
* @return The currently configured default command
*/
public Command getDefaultCommand() {
return m_registeredScheduler.getDefaultCommandFor(this);
}
/**
* Starts building a command that requires this mechanism.
*
* @param commandBody The main function body of the command.
* @return The command builder, for further configuration.
*/
public NeedsNameBuilderStage run(Consumer<Coroutine> commandBody) {
return new StagedCommandBuilder().requiring(this).executing(commandBody);
}
/**
* Starts building a command that requires this mechanism. The given function will be called
* repeatedly in an infinite loop. Useful for building commands that don't need state or multiple
* stages of logic.
*
* @param loopBody The body of the infinite loop.
* @return The command builder, for further configuration.
*/
public NeedsNameBuilderStage runRepeatedly(Runnable loopBody) {
return run(
coroutine -> {
while (true) {
loopBody.run();
coroutine.yield();
}
});
}
/**
* Returns a command that idles this mechanism until another command claims it. The idle command
* has {@link Command#LOWEST_PRIORITY the lowest priority} and can be interrupted by any other
* command.
*
* <p>The {@link #getDefaultCommand() default command} for every mechanism is an idle command
* unless a different default command has been configured.
*
* @return A new idle command.
*/
public Command idle() {
return run(Coroutine::park).withPriority(Command.LOWEST_PRIORITY).named(getName() + "[IDLE]");
}
/**
* Returns a command that idles this mechanism for the given duration of time.
*
* @param duration How long the mechanism should idle for.
* @return A new idle command.
*/
public Command idleFor(Time duration) {
return idle().withTimeout(duration);
}
/**
* Gets all running commands that require this mechanism. Commands are returned in the order in
* which they were scheduled. The returned list is read-only. Every command in the list will have
* been scheduled by the previous entry in the list or by intermediate commands that do not
* require the mechanism.
*
* @return The currently running commands that require the mechanism.
*/
@NoDiscard
public List<Command> getRunningCommands() {
return m_registeredScheduler.getRunningCommandsFor(this);
}
@Override
public String toString() {
return m_name;
}
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import java.util.Collection;
import java.util.function.Consumer;
import org.wpilib.annotation.NoDiscard;
/**
* A stage in a command builder where requirements have already been specified and execution details
* are required. The next stage is a {@link NeedsNameBuilderStage} from {@link
* #executing(Consumer)}. Additional requirements may still be added before moving on to the next
* stage.
*/
@NoDiscard
public interface NeedsExecutionBuilderStage {
/**
* Adds a required mechanism for the command.
*
* @param requirement A required mechanism. Cannot be null.
* @return This builder object, for chaining
* @throws NullPointerException If {@code requirement} is null
*/
NeedsExecutionBuilderStage requiring(Mechanism requirement);
/**
* Adds one or more required mechanisms for the command.
*
* @param requirement A required mechanism. Cannot be null.
* @param extra Any extra required mechanisms. May be empty, but cannot contain null values.
* @return This builder object, for chaining
* @throws NullPointerException If {@code requirement} is null or {@code extra} contains a null
* value
*/
NeedsExecutionBuilderStage requiring(Mechanism requirement, Mechanism... extra);
/**
* Adds required mechanisms for the command.
*
* @param requirements Any required mechanisms. May be empty, but cannot contain null values.
* @return This builder object, for chaining
* @throws NullPointerException If {@code requirements} is null or contains a null value.
*/
NeedsExecutionBuilderStage requiring(Collection<Mechanism> requirements);
/**
* Sets the function body of the executing command.
*
* @param impl The command's body. Cannot be null.
* @return A builder for the next stage of command construction.
* @throws NullPointerException If {@code impl} is null.
*/
NeedsNameBuilderStage executing(Consumer<Coroutine> impl);
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import org.wpilib.annotation.NoDiscard;
/**
* A stage in a command builder where requirements and main command execution logic have been set.
* No more changes to requirements or command implementation may happen after this point. This is
* the final step in command creation
*/
@NoDiscard
public interface NeedsNameBuilderStage {
/**
* Optionally sets a callback to execute when the command is canceled. The callback will not run
* if the command was canceled after being scheduled but before starting to run, and will not run
* if the command completes naturally or from encountering an unhandled exception.
*
* @param onCancel The function to execute when the command is canceled while running. May be
* null.
* @return This builder object, for chaining
*/
NeedsNameBuilderStage whenCanceled(Runnable onCancel);
/**
* Sets the priority level of the command.
*
* @param priority The desired priority level.
* @return This builder object, for chaining.
*/
NeedsNameBuilderStage withPriority(int priority);
/**
* Optionally sets an end condition for the command. If the end condition returns {@code true}
* before the command body set in {@link NeedsExecutionBuilderStage#executing(Consumer)} would
* exit, the command will be canceled by the scheduler.
*
* @param endCondition An optional end condition for the command. May be null.
* @return This builder object, for chaining.
*/
NeedsNameBuilderStage until(BooleanSupplier endCondition);
/**
* Creates the command based on the options set during previous builder stages. The builders will
* no longer be usable after calling this method.
*
* @param name The name of the command
* @return The built command.
* @throws NullPointerException If {@code name} is null.
*/
Command named(String name);
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* A type of command that runs multiple other commands in parallel. The group will end after all
* required commands have completed; if no command is explicitly required for completion, then the
* group will end after any command in the group finishes. Any commands still running when the group
* has reached its completion condition will be canceled.
*/
public final class ParallelGroup implements Command {
private final Collection<Command> m_requiredCommands = new HashSet<>();
private final Collection<Command> m_optionalCommands = new LinkedHashSet<>();
private final Set<Mechanism> m_requirements = new HashSet<>();
private final String m_name;
private final int m_priority;
/**
* Creates a new parallel group.
*
* @param name The name of the group.
* @param requiredCommands The commands that are required to complete for the group to finish. If
* this is empty, then the group will finish when <i>any</i> optional command completes.
* @param optionalCommands The commands that do not need to complete for the group to finish. If
* this is empty, then the group will finish when <i>all</i> required commands complete.
*/
ParallelGroup(
String name, Collection<Command> requiredCommands, Collection<Command> optionalCommands) {
requireNonNullParam(name, "name", "ParallelGroup");
requireNonNullParam(requiredCommands, "requiredCommands", "ParallelGroup");
requireNonNullParam(optionalCommands, "optionalCommands", "ParallelGroup");
int i = 0;
for (Command requiredCommand : requiredCommands) {
requireNonNullParam(requiredCommand, "requiredCommands[" + i + "]", "ParallelGroup");
i++;
}
i = 0;
for (Command c : optionalCommands) {
requireNonNullParam(c, "optionalCommands[" + i + "]", "ParallelGroup");
i++;
}
var allCommands = new LinkedHashSet<Command>();
allCommands.addAll(requiredCommands);
allCommands.addAll(optionalCommands);
ConflictDetector.throwIfConflicts(allCommands);
m_name = name;
m_optionalCommands.addAll(optionalCommands);
m_requiredCommands.addAll(requiredCommands);
for (var command : allCommands) {
m_requirements.addAll(command.requirements());
}
m_priority =
allCommands.stream().mapToInt(Command::priority).max().orElse(Command.DEFAULT_PRIORITY);
}
@Override
public void run(Coroutine coroutine) {
coroutine.fork(m_optionalCommands);
if (m_requiredCommands.isEmpty()) {
// No required commands - just wait for the first optional command to finish
coroutine.awaitAny(m_optionalCommands);
} else {
// Wait for every required command to finish
coroutine.awaitAll(m_requiredCommands);
}
// The scheduler will cancel any optional child commands that are still running when
// the `run` method returns
}
@Override
public String name() {
return m_name;
}
@Override
public Set<Mechanism> requirements() {
return m_requirements;
}
@Override
public int priority() {
return m_priority;
}
@Override
public String toString() {
return "ParallelGroup[name=" + m_name + "]";
}
}

View File

@@ -0,0 +1,147 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import org.wpilib.annotation.NoDiscard;
/**
* A builder class to configure and then create a {@link ParallelGroup}. Like {@link
* StagedCommandBuilder}, the final command is created by calling the terminal {@link
* #named(String)} method, or with an automatically generated name using {@link
* #withAutomaticName()}.
*/
@NoDiscard
public class ParallelGroupBuilder {
private final Set<Command> m_commands = new LinkedHashSet<>();
private final Set<Command> m_requiredCommands = new LinkedHashSet<>();
private BooleanSupplier m_endCondition;
/**
* Creates a new parallel group builder. The builder will have no commands and have no preapplied
* configuration options.
*/
public ParallelGroupBuilder() {}
/**
* Adds one or more optional commands to the group. They will not be required to complete for the
* parallel group to exit, and will be canceled once all required commands have finished.
*
* @param commands The optional commands to add to the group
* @return The builder object, for chaining
*/
public ParallelGroupBuilder optional(Command... commands) {
requireNonNullParam(commands, "commands", "ParallelGroupBuilder.optional");
for (int i = 0; i < commands.length; i++) {
requireNonNullParam(commands[i], "commands[" + i + "]", "ParallelGroupBuilder.optional");
}
m_commands.addAll(Arrays.asList(commands));
return this;
}
/**
* Adds one or more required commands to the group. All required commands will need to complete
* for the group to exit.
*
* @param commands The required commands to add to the group
* @return The builder object, for chaining
*/
public ParallelGroupBuilder requiring(Command... commands) {
requireNonNullParam(commands, "commands", "ParallelGroupBuilder.requiring");
for (int i = 0; i < commands.length; i++) {
requireNonNullParam(commands[i], "commands[" + i + "]", "ParallelGroupBuilder.requiring");
}
m_requiredCommands.addAll(Arrays.asList(commands));
m_commands.addAll(m_requiredCommands);
return this;
}
/**
* Adds a command to the group. The command must complete for the group to exit.
*
* @param command The command to add to the group
* @return The builder object, for chaining
*/
// Note: this primarily exists so users can cleanly chain .alongWith calls to build a
// parallel group, eg `fooCommand().alongWith(barCommand()).alongWith(bazCommand())`
public ParallelGroupBuilder alongWith(Command command) {
return requiring(command);
}
/**
* Adds an end condition to the command group. If this condition is met before all required
* commands have completed, the group will exit early. If multiple end conditions are added (e.g.
* {@code .until(() -> conditionA()).until(() -> conditionB())}), then the last end condition
* added will be used and any previously configured condition will be overridden.
*
* @param condition The end condition for the group. May be null.
* @return The builder object, for chaining
*/
public ParallelGroupBuilder until(BooleanSupplier condition) {
m_endCondition = condition;
return this;
}
/**
* Creates the group, using the provided named. The group will require everything that the
* commands in the group require, and will have a priority level equal to the highest priority
* among those commands.
*
* @param name The name of the parallel group
* @return The built group
*/
public ParallelGroup named(String name) {
requireNonNullParam(name, "name", "ParallelGroupBuilder.named");
var group = new ParallelGroup(name, m_commands, m_requiredCommands);
if (m_endCondition == null) {
// No custom end condition, return the group as is
return group;
}
// We have a custom end condition, so we need to wrap the group in a race
return new ParallelGroupBuilder()
.optional(group, Command.waitUntil(m_endCondition).named("Until Condition"))
.named(name);
}
/**
* Creates the group, giving it a name based on the commands within the group.
*
* @return The built group
*/
public ParallelGroup withAutomaticName() {
// eg "(CommandA & CommandB & CommandC)"
String required =
m_requiredCommands.stream().map(Command::name).collect(Collectors.joining(" & ", "(", ")"));
var optionalCommands = new LinkedHashSet<>(m_commands);
optionalCommands.removeAll(m_requiredCommands);
// eg "(CommandA | CommandB | CommandC)"
String optional =
optionalCommands.stream().map(Command::name).collect(Collectors.joining(" | ", "(", ")"));
if (m_requiredCommands.isEmpty()) {
// No required commands, pure race
return named(optional);
} else if (optionalCommands.isEmpty()) {
// Everything required
return named(required);
} else {
// Some form of deadline
// eg "[(CommandA & CommandB) * (CommandX | CommandY | ...)]"
String name = "[%s * %s]".formatted(required, optional);
return named(name);
}
}
}

View File

@@ -0,0 +1,970 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.units.Units.Microseconds;
import static edu.wpi.first.units.Units.Milliseconds;
import edu.wpi.first.util.ErrorMessages;
import edu.wpi.first.util.protobuf.ProtobufSerializable;
import edu.wpi.first.wpilibj.RobotController;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.event.EventLoop;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.SequencedMap;
import java.util.SequencedSet;
import java.util.Set;
import java.util.Stack;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.wpilib.annotation.NoDiscard;
import org.wpilib.commands3.button.CommandGenericHID;
import org.wpilib.commands3.proto.SchedulerProto;
/**
* Manages the lifecycles of {@link Coroutine}-based {@link Command Commands}. Commands may be
* scheduled directly using {@link #schedule(Command)}, or be bound to {@link Trigger Triggers} to
* automatically handle scheduling and cancellation based on internal or external events. User code
* is responsible for calling {@link #run()} periodically to update trigger conditions and execute
* scheduled commands. Most often, this is done by overriding {@link TimedRobot#robotPeriodic()} to
* include a call to {@code Scheduler.getDefault().run()}:
*
* <pre>{@code
* public class Robot extends TimedRobot {
* @Override
* public void robotPeriodic() {
* // Update the scheduler on every loop
* Scheduler.getDefault().run();
* }
* }
* }</pre>
*
* <h2>Danger</h2>
*
* <p>The scheduler <i>must</i> be used in a single-threaded program. Commands must be scheduled and
* canceled by the same thread that runs the scheduler, and cannot be run in a virtual thread.
*
* <p><strong>Using the commands framework in a multithreaded environment can cause crashes in the
* Java virtual machine at any time, including on an official field during a match.</strong> The
* Java JIT compilers make assumptions that rely on coroutines being used on a single thread.
* Breaking those assumptions can cause incorrect JIT code to be generated with undefined behavior,
* potentially causing control issues or crashes deep in JIT-generated native code.
*
* <p><strong>Normal concurrency constructs like locks, atomic references, and synchronized blocks
* or methods cannot save you.</strong>
*
* <h2>Lifecycle</h2>
*
* <p>The {@link #run()} method runs five steps:
*
* <ol>
* <li>Call {@link #sideload(Consumer) periodic sideload functions}
* <li>Poll all registered triggers to queue and cancel commands
* <li>Queue default commands for any mechanisms without a running command. The queued commands
* can be superseded by any manual scheduling or commands scheduled by triggers in the next
* run.
* <li>Start all queued commands. This happens after all triggers are checked in case multiple
* commands with conflicting requirements are queued in the same update; the last command to
* be queued takes precedence over the rest.
* <li>Loop over all running commands, mounting and calling each in turn until they either exit or
* call {@link Coroutine#yield()}. Commands run in the order in which they were scheduled.
* </ol>
*
* <h2>Telemetry</h2>
*
* <p>There are two mechanisms for telemetry for a scheduler. A protobuf serializer can be used to
* take a snapshot of a scheduler instance, and report what commands are queued (scheduled but have
* not yet started to run), commands that are running (along with timing data for each command), and
* total time spent in the most recent {@link #run()} call. However, it cannot detect one-shot
* commands that are scheduled, run, and complete all in a single {@code run()} invocation -
* effectively, commands that never call {@link Coroutine#yield()} are invisible.
*
* <p>A second telemetry mechanism is provided by {@link #addEventListener(Consumer)}. The scheduler
* will issue events to all registered listeners when certain events occur (see {@link
* SchedulerEvent} for all event types). These events are emitted immediately and can be used to
* detect lifecycle events for all commands, including one-shots that would be invisible to the
* protobuf serializer. However, it is up to the user to log those events themselves.
*/
public final class Scheduler implements ProtobufSerializable {
private final Map<Mechanism, Command> m_defaultCommands = new LinkedHashMap<>();
/** The set of commands scheduled since the start of the previous run. */
private final SequencedSet<CommandState> m_queuedToRun = new LinkedHashSet<>();
/**
* The states of all running commands (does not include on deck commands). We preserve insertion
* order to guarantee that child commands run after their parents.
*/
private final SequencedMap<Command, CommandState> m_runningCommands = new LinkedHashMap<>();
/**
* The stack of currently executing commands. Child commands are pushed onto the stack and popped
* when they complete. Use {@link #currentState()} and {@link #currentCommand()} to get the
* currently executing command or its state.
*/
private final Stack<CommandState> m_currentCommandAncestry = new Stack<>();
/** The periodic callbacks to run, outside of the command structure. */
private final List<Coroutine> m_periodicCallbacks = new ArrayList<>();
/** Event loop for trigger bindings. */
private final EventLoop m_eventLoop = new EventLoop();
/** The scope for continuations to yield to. */
private final ContinuationScope m_scope = new ContinuationScope("coroutine commands");
// Telemetry
/** Protobuf serializer for a scheduler. */
public static final SchedulerProto proto = new SchedulerProto();
private double m_lastRunTimeMs = -1;
private final Set<Consumer<? super SchedulerEvent>> m_eventListeners = new LinkedHashSet<>();
/** The default scheduler instance. */
private static final Scheduler s_defaultScheduler = new Scheduler();
/**
* Gets the default scheduler instance for use in a robot program. Unless otherwise specified,
* triggers and mechanisms will be registered with the default scheduler and require the default
* scheduler to run. However, triggers and mechanisms can be constructed to be registered with a
* specific scheduler instance, which may be useful for isolation for unit tests.
*
* @return the default scheduler instance.
*/
@NoDiscard
public static Scheduler getDefault() {
return s_defaultScheduler;
}
/**
* Creates a new scheduler object. Note that most built-in constructs like {@link Trigger} and
* {@link CommandGenericHID} will use the {@link #getDefault() default scheduler instance} unless
* they were explicitly constructed with a different scheduler instance. Teams should use the
* default instance for convenience; however, new scheduler instances can be useful for unit
* tests.
*
* @return a new scheduler instance that is independent of the default scheduler instance.
*/
@NoDiscard
public static Scheduler createIndependentScheduler() {
return new Scheduler();
}
/** Private constructor. Use static factory methods or the default scheduler instance. */
private Scheduler() {}
/**
* Sets the default command for a mechanism. The command must require that mechanism, and cannot
* require any other mechanisms.
*
* @param mechanism the mechanism for which to set the default command
* @param defaultCommand the default command to execute on the mechanism
* @throws IllegalArgumentException if the command does not meet the requirements for being a
* default command
*/
public void setDefaultCommand(Mechanism mechanism, Command defaultCommand) {
if (!defaultCommand.requires(mechanism)) {
throw new IllegalArgumentException(
"A mechanism's default command must require that mechanism");
}
if (defaultCommand.requirements().size() > 1) {
throw new IllegalArgumentException(
"A mechanism's default command cannot require other mechanisms");
}
m_defaultCommands.put(mechanism, defaultCommand);
}
/**
* Gets the default command set for a mechanism.
*
* @param mechanism The mechanism
* @return The default command, or null if no default command was ever set
*/
public Command getDefaultCommandFor(Mechanism mechanism) {
return m_defaultCommands.get(mechanism);
}
/**
* Adds a callback to run as part of the scheduler. The callback should not manipulate or control
* any mechanisms, but can be used to log information, update data (such as simulations or LED
* data buffers), or perform some other helpful task. The callback is responsible for managing its
* own control flow and end conditions. If you want to run a single task periodically for the
* entire lifespan of the scheduler, use {@link #addPeriodic(Runnable)}.
*
* <p><strong>Note:</strong> Like commands, any loops in the callback must appropriately yield
* control back to the scheduler with {@link Coroutine#yield} or risk stalling your program in an
* unrecoverable infinite loop!
*
* @param callback the callback to sideload
*/
public void sideload(Consumer<Coroutine> callback) {
var coroutine = new Coroutine(this, m_scope, callback);
m_periodicCallbacks.add(coroutine);
}
/**
* Adds a task to run repeatedly for as long as the scheduler runs. This internally handles the
* looping and control yielding necessary for proper function. The callback will run at the same
* periodic frequency as the scheduler.
*
* <p>For example:
*
* <pre>{@code
* scheduler.addPeriodic(() -> leds.setData(ledDataBuffer));
* scheduler.addPeriodic(() -> {
* SmartDashboard.putNumber("X", getX());
* SmartDashboard.putNumber("Y", getY());
* });
* }</pre>
*
* @param callback the periodic function to run
*/
public void addPeriodic(Runnable callback) {
sideload(
coroutine -> {
while (true) {
callback.run();
coroutine.yield();
}
});
}
/** Represents possible results of a command scheduling attempt. */
public enum ScheduleResult {
/** The command was successfully scheduled and added to the queue. */
SUCCESS,
/** The command is already scheduled or running. */
ALREADY_RUNNING,
/** The command is a lower priority and conflicts with a command that's already running. */
LOWER_PRIORITY_THAN_RUNNING_COMMAND,
}
/**
* Schedules a command to run. If one command schedules another (a "parent" and "child"), the
* child command will be canceled when the parent command completes. It is not possible to fork a
* child command and have it live longer than its parent.
*
* <p>Does nothing if the command is already scheduled or running, or requires at least one
* mechanism already used by a higher priority command.
*
* @param command the command to schedule
* @return the result of the scheduling attempt. See {@link ScheduleResult} for details.
* @throws IllegalArgumentException if scheduled by a command composition that has already
* scheduled another command that shares at least one required mechanism
*/
// Implementation detail: a child command will immediately start running when scheduled by a
// parent command, skipping the queue entirely. This avoids dead loop cycles where a parent
// schedules a child, appending it to the queue, then waits for the next cycle to pick it up and
// start it. With deeply nested commands, dead loops could quickly to add up and cause the
// innermost commands that actually _do_ something to start running hundreds of milliseconds after
// their root ancestor was scheduled.
public ScheduleResult schedule(Command command) {
// Note: we use a throwable here instead of Thread.currentThread().getStackTrace() for easier
// stack frame filtering and modification.
var binding =
new Binding(
BindingScope.global(), BindingType.IMMEDIATE, command, new Throwable().getStackTrace());
return schedule(binding);
}
// Package-private for use by Trigger
ScheduleResult schedule(Binding binding) {
var command = binding.command();
if (isScheduledOrRunning(command)) {
return ScheduleResult.ALREADY_RUNNING;
}
if (lowerPriorityThanConflictingCommands(command)) {
return ScheduleResult.LOWER_PRIORITY_THAN_RUNNING_COMMAND;
}
for (var scheduledState : m_queuedToRun) {
if (!command.conflictsWith(scheduledState.command())) {
// No shared requirements, skip
continue;
}
if (command.isLowerPriorityThan(scheduledState.command())) {
// Lower priority than an already-scheduled (but not yet running) command that requires at
// one of the same mechanism. Ignore it.
return ScheduleResult.LOWER_PRIORITY_THAN_RUNNING_COMMAND;
}
}
// Evict conflicting on-deck commands
// We check above if the input command is lower priority than any of these,
// so at this point we're guaranteed to be >= priority than anything already on deck
evictConflictingOnDeckCommands(command);
// If the binding is scoped to a particular command, that command is the parent. If we're in the
// middle of a run cycle and running commands, the parent is whatever command is currently
// running. Otherwise, there is no parent command.
var parentCommand =
binding.scope() instanceof BindingScope.ForCommand scope
? scope.command()
: currentCommand();
var state = new CommandState(command, parentCommand, buildCoroutine(command), binding);
emitScheduledEvent(command);
if (currentState() != null) {
// Scheduling a child command while running. Start it immediately instead of waiting a full
// cycle. This prevents issues with deeply nested command groups taking many scheduler cycles
// to start running the commands that actually _do_ things
evictConflictingRunningCommands(state);
m_runningCommands.put(command, state);
runCommand(state);
} else {
// Scheduling outside a command, add it to the pending set. If it's not overridden by another
// conflicting command being scheduled in the same scheduler loop, it'll be promoted and
// start to run when #runCommands() is called
m_queuedToRun.add(state);
}
return ScheduleResult.SUCCESS;
}
/**
* Checks if a command conflicts with and is a lower priority than any running command. Used when
* determining if the command can be scheduled.
*/
private boolean lowerPriorityThanConflictingCommands(Command command) {
Set<CommandState> ancestors = new HashSet<>();
for (var state = currentState(); state != null; state = m_runningCommands.get(state.parent())) {
ancestors.add(state);
}
// Check for conflicts with the commands that are already running
for (var state : m_runningCommands.values()) {
if (ancestors.contains(state)) {
// Can't conflict with an ancestor command
continue;
}
var c = state.command();
if (c.conflictsWith(command) && command.isLowerPriorityThan(c)) {
return true;
}
}
return false;
}
private void evictConflictingOnDeckCommands(Command command) {
for (var iterator = m_queuedToRun.iterator(); iterator.hasNext(); ) {
var scheduledState = iterator.next();
var scheduledCommand = scheduledState.command();
if (scheduledCommand.conflictsWith(command)) {
// Remove the lower priority conflicting command from the on deck commands.
// We don't need to call removeOrphanedChildren here because it hasn't started yet,
// meaning it hasn't had a chance to schedule any children
iterator.remove();
emitInterruptedEvent(scheduledCommand, command);
emitCanceledEvent(scheduledCommand);
}
}
}
/**
* Cancels all running commands with which an incoming state conflicts. Ancestor commands of the
* incoming state will not be canceled.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
private void evictConflictingRunningCommands(CommandState incomingState) {
// The set of root states with which the incoming state conflicts but does not inherit from
Set<CommandState> conflictingRootStates =
m_runningCommands.values().stream()
.filter(state -> incomingState.command().conflictsWith(state.command()))
.filter(state -> !isAncestorOf(state.command(), incomingState))
.map(
state -> {
// Find the highest level ancestor of the conflicting command from which the
// incoming state does _not_ inherit. If they're totally unrelated, this will
// get the very root ancestor; otherwise, it'll return a direct sibling of the
// incoming command
CommandState root = state;
while (root.parent() != null && root.parent() != incomingState.parent()) {
root = m_runningCommands.get(root.parent());
}
return root;
})
.collect(Collectors.toSet());
// Cancel the root commands
for (var conflictingState : conflictingRootStates) {
emitInterruptedEvent(conflictingState.command(), incomingState.command());
cancel(conflictingState.command());
}
}
/**
* Checks if a particular command is an ancestor of another.
*
* @param ancestor the potential ancestor for which to search
* @param state the state to check
* @return true if {@code ancestor} is the direct parent or indirect ancestor, false if not
*/
@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.SimplifyBooleanReturns"})
private boolean isAncestorOf(Command ancestor, CommandState state) {
if (state.parent() == null) {
// No parent, cannot inherit
return false;
}
if (!m_runningCommands.containsKey(ancestor)) {
// The given ancestor isn't running
return false;
}
if (state.parent() == ancestor) {
// Direct child
return true;
}
// Check if the command's parent inherits from the given ancestor
return m_runningCommands.values().stream()
.filter(s -> state.parent() == s.command())
.anyMatch(s -> isAncestorOf(ancestor, s));
}
/**
* Cancels a command and any other command scheduled by it. This occurs immediately and does not
* need to wait for a call to {@link #run()}. Any command that it scheduled will also be canceled
* to ensure commands within compositions do not continue to run.
*
* <p>This has no effect if the given command is not currently scheduled or running.
*
* @param command the command to cancel
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public void cancel(Command command) {
if (command == currentCommand()) {
throw new IllegalArgumentException(
"Command `" + command.name() + "` is mounted and cannot be canceled");
}
boolean running = isRunning(command);
// Evict the command. The next call to run() will schedule the default command for all its
// required mechanisms, unless another command requiring those mechanisms is scheduled between
// calling cancel() and calling run()
m_runningCommands.remove(command);
m_queuedToRun.removeIf(state -> state.command() == command);
if (running) {
// Only run the hook if the command was running. If it was on deck or not
// even in the scheduler at the time, then there's nothing to do
command.onCancel();
emitCanceledEvent(command);
}
// Clean up any orphaned child commands; their lifespan must not exceed the parent's
removeOrphanedChildren(command);
}
/**
* Updates the command scheduler. This will run operations in the following order:
*
* <ol>
* <li>Run sideloaded functions from {@link #sideload(Consumer)} and {@link
* #addPeriodic(Runnable)}
* <li>Update trigger bindings to queue and cancel bound commands
* <li>Queue default commands for mechanisms that do not have a queued or running command
* <li>Promote queued commands to the running set
* <li>For every command in the running set, mount and run that command until it calls {@link
* Coroutine#yield()} or exits
* </ol>
*
* <p>This method is intended to be called in a periodic loop like {@link
* TimedRobot#robotPeriodic()}
*/
public void run() {
final long startMicros = RobotController.getTime();
// Sideloads may change some state that affects triggers. Run them first.
runPeriodicSideloads();
// Poll triggers next to schedule and cancel commands
m_eventLoop.poll();
// Schedule default commands for any mechanisms that don't have a running command and didn't
// have a new command scheduled by a sideload function or a trigger
scheduleDefaultCommands();
// Move all scheduled commands to the running set
promoteScheduledCommands();
// Run every command in order until they call Coroutine.yield() or exit
runCommands();
final long endMicros = RobotController.getTime();
m_lastRunTimeMs = Milliseconds.convertFrom(endMicros - startMicros, Microseconds);
}
private void promoteScheduledCommands() {
// Clear any commands that conflict with the scheduled set
for (var queuedState : m_queuedToRun) {
evictConflictingRunningCommands(queuedState);
}
// Move any scheduled commands to the running set
for (var queuedState : m_queuedToRun) {
m_runningCommands.put(queuedState.command(), queuedState);
}
// Clear the set of on-deck commands,
// since we just put them all into the set of running commands
m_queuedToRun.clear();
}
private void runPeriodicSideloads() {
// Update periodic callbacks
for (Coroutine coroutine : m_periodicCallbacks) {
coroutine.mount();
try {
coroutine.runToYieldPoint();
} finally {
Continuation.mountContinuation(null);
}
}
// And remove any periodic callbacks that have completed
m_periodicCallbacks.removeIf(Coroutine::isDone);
}
private void runCommands() {
// Tick every command that hasn't been completed yet
// Run in reverse so parent commands can resume in the same loop cycle an awaited child command
// completes. Otherwise, parents could only resume on the next loop cycle, introducing a delay
// at every layer of nesting.
for (var state : List.copyOf(m_runningCommands.values()).reversed()) {
runCommand(state);
}
}
/**
* Mounts and runs a command until it yields or exits.
*
* @param state The command state to run
*/
@SuppressWarnings("PMD.AvoidCatchingGenericException")
private void runCommand(CommandState state) {
final var command = state.command();
final var coroutine = state.coroutine();
if (!m_runningCommands.containsKey(command)) {
// Probably canceled by an owning composition, do not run
return;
}
var previousState = currentState();
m_currentCommandAncestry.push(state);
long startMicros = RobotController.getTime();
emitMountedEvent(command);
coroutine.mount();
try {
coroutine.runToYieldPoint();
} catch (RuntimeException e) {
// Command encountered an uncaught exception.
handleCommandException(state, e);
} finally {
long endMicros = RobotController.getTime();
double elapsedMs = Milliseconds.convertFrom(endMicros - startMicros, Microseconds);
state.setLastRuntimeMs(elapsedMs);
if (state.equals(currentState())) {
// Remove the command we just ran from the top of the stack
m_currentCommandAncestry.pop();
}
if (previousState != null) {
// Remount the parent command, if there is one
previousState.coroutine().mount();
} else {
Continuation.mountContinuation(null);
}
}
if (coroutine.isDone()) {
// Immediately check if the command has completed and remove any children commands.
// This prevents child commands from being executed one extra time in the run() loop
emitCompletedEvent(command);
m_runningCommands.remove(command);
removeOrphanedChildren(command);
} else {
// Yielded
emitYieldedEvent(command);
}
}
/**
* Handles uncaught runtime exceptions from a mounted and running command. The command's ancestor
* and child commands will all be canceled and the exception's backtrace will be modified to
* include the stack frames of the schedule call site.
*
* @param state The state of the command that encountered the exception.
* @param e The exception that was thrown.
* @throws RuntimeException rethrows the exception, with a modified backtrace pointing to the
* schedule site
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
private void handleCommandException(CommandState state, RuntimeException e) {
var command = state.command();
// Fetch the root command
// (needs to be done before removing the failed command from the running set)
Command root = command;
while (getParentOf(root) != null) {
root = getParentOf(root);
}
// Remove it from the running set.
m_runningCommands.remove(command);
// Intercept the exception, inject stack frames from the schedule site, and rethrow it
var binding = state.binding();
e.setStackTrace(CommandTraceHelper.modifyTrace(e.getStackTrace(), binding.frames()));
emitCompletedWithErrorEvent(command, e);
// Clean up child commands after emitting the event so child Canceled events are emitted
// after the parent's CompletedWithError
removeOrphanedChildren(command);
// Bubble up to the root and cancel all commands between the root and this one
// Note: Because we remove the command from the running set above, we still need to
// clean up all the failed command's children
if (root != null && root != command) {
cancel(root);
}
// Then rethrow the exception
throw e;
}
/**
* Gets the currently executing command state, or null if no command is currently executing.
*
* @return the currently executing command state
*/
private CommandState currentState() {
if (m_currentCommandAncestry.isEmpty()) {
// Avoid EmptyStackException
return null;
}
return m_currentCommandAncestry.peek();
}
/**
* Gets the currently executing command, or null if no command is currently executing.
*
* @return the currently executing command
*/
public Command currentCommand() {
var state = currentState();
if (state == null) {
return null;
}
return state.command();
}
private void scheduleDefaultCommands() {
// Schedule the default commands for every mechanism that doesn't currently have a running or
// scheduled command.
m_defaultCommands.forEach(
(mechanism, defaultCommand) -> {
if (m_runningCommands.keySet().stream().noneMatch(c -> c.requires(mechanism))
&& m_queuedToRun.stream().noneMatch(c -> c.command().requires(mechanism))
&& defaultCommand != null) {
// Nothing currently running or scheduled
// Schedule the mechanism's default command, if it has one
schedule(defaultCommand);
}
});
}
/**
* Removes all commands descended from a parent command. This is used to ensure that any command
* scheduled within a composition or group cannot live longer than any ancestor.
*
* @param parent the root command whose descendants to remove from the scheduler
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
private void removeOrphanedChildren(Command parent) {
m_runningCommands.entrySet().stream()
.filter(e -> e.getValue().parent() == parent)
.toList() // copy to an intermediate list to avoid concurrent modification
.forEach(e -> cancel(e.getKey()));
}
/**
* Builds a coroutine object that the command will be bound to. The coroutine will be scoped to
* this scheduler object and cannot be used by another scheduler instance.
*
* @param command the command for which to build a coroutine
* @return the binding coroutine
*/
private Coroutine buildCoroutine(Command command) {
return new Coroutine(this, m_scope, command::run);
}
/**
* Checks if a command is currently running.
*
* @param command the command to check
* @return true if the command is running, false if not
*/
public boolean isRunning(Command command) {
return m_runningCommands.containsKey(command);
}
/**
* Checks if a command is currently scheduled to run, but is not yet running.
*
* @param command the command to check
* @return true if the command is scheduled to run, false if not
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public boolean isScheduled(Command command) {
return m_queuedToRun.stream().anyMatch(state -> state.command() == command);
}
/**
* Checks if a command is currently scheduled to run, or is already running.
*
* @param command the command to check
* @return true if the command is scheduled to run or is already running, false if not
*/
public boolean isScheduledOrRunning(Command command) {
return isScheduled(command) || isRunning(command);
}
/**
* Gets the set of all currently running commands. Commands are returned in the order in which
* they were scheduled. The returned set is read-only.
*
* @return the currently running commands
*/
public Collection<Command> getRunningCommands() {
return Collections.unmodifiableSet(m_runningCommands.keySet());
}
/**
* Gets all the currently running commands that require a particular mechanism. Commands are
* returned in the order in which they were scheduled. The returned list is read-only.
*
* @param mechanism the mechanism to get the commands for
* @return the currently running commands that require the mechanism.
*/
public List<Command> getRunningCommandsFor(Mechanism mechanism) {
return m_runningCommands.keySet().stream()
.filter(command -> command.requires(mechanism))
.toList();
}
/**
* Cancels all currently running and scheduled commands. All default commands will be scheduled on
* the next call to {@link #run()}, unless a higher priority command is scheduled or triggered
* after {@code cancelAll()} is used.
*/
public void cancelAll() {
for (var onDeckIter = m_queuedToRun.iterator(); onDeckIter.hasNext(); ) {
var state = onDeckIter.next();
onDeckIter.remove();
emitCanceledEvent(state.command());
}
for (var liveIter = m_runningCommands.entrySet().iterator(); liveIter.hasNext(); ) {
var entry = liveIter.next();
liveIter.remove();
Command canceledCommand = entry.getKey();
canceledCommand.onCancel();
emitCanceledEvent(canceledCommand);
}
}
/**
* An event loop used by the scheduler to poll triggers that schedule or cancel commands. This
* event loop is always polled on every call to {@link #run()}. Custom event loops need to be
* bound to this one for synchronicity with the scheduler; however, they can always be polled
* manually before or after the call to {@link #run()}.
*
* @return the default event loop.
*/
@NoDiscard
public EventLoop getDefaultEventLoop() {
return m_eventLoop;
}
/**
* For internal use.
*
* @return The commands that have been scheduled but not yet started.
*/
@NoDiscard
public Collection<Command> getQueuedCommands() {
return m_queuedToRun.stream().map(CommandState::command).toList();
}
/**
* For internal use.
*
* @param command The command to check
* @return The command that forked the provided command. Null if the command is not a child of
* another command.
*/
public Command getParentOf(Command command) {
var state = m_runningCommands.get(command);
if (state == null) {
return null;
}
return state.parent();
}
/**
* Gets how long a command took to run in the previous cycle. If the command is not currently
* running, returns -1.
*
* @param command The command to check
* @return How long, in milliseconds, the command last took to execute.
*/
public double lastCommandRuntimeMs(Command command) {
if (m_runningCommands.containsKey(command)) {
return m_runningCommands.get(command).lastRuntimeMs();
} else {
return -1;
}
}
/**
* Gets how long a command has taken to run, in aggregate, since it was most recently scheduled.
* If the command is not currently running, returns -1.
*
* @param command The command to check
* @return How long, in milliseconds, the command has taken to execute in total
*/
public double totalRuntimeMs(Command command) {
if (m_runningCommands.containsKey(command)) {
return m_runningCommands.get(command).totalRuntimeMs();
} else {
// Not running; no data
return -1;
}
}
/**
* Gets the unique run id for a scheduled or running command. If the command is not currently
* scheduled or running, this function returns {@code 0}.
*
* @param command The command to get the run ID for
* @return The run of the command
*/
@NoDiscard
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public int runId(Command command) {
if (m_runningCommands.containsKey(command)) {
return m_runningCommands.get(command).id();
}
// Check scheduled commands
for (var scheduled : m_queuedToRun) {
if (scheduled.command() == command) {
return scheduled.id();
}
}
return 0;
}
/**
* Gets how long the scheduler took to process its most recent {@link #run()} invocation, in
* milliseconds. Defaults to -1 if the scheduler has not yet run.
*
* @return How long, in milliseconds, the scheduler last took to execute.
*/
@NoDiscard
public double lastRuntimeMs() {
return m_lastRunTimeMs;
}
// Event-base telemetry and helpers. The static factories are for convenience to automatically
// set the timestamp instead of littering RobotController.getTime() everywhere.
private void emitScheduledEvent(Command command) {
var event = new SchedulerEvent.Scheduled(command, RobotController.getTime());
emitEvent(event);
}
private void emitMountedEvent(Command command) {
var event = new SchedulerEvent.Mounted(command, RobotController.getTime());
emitEvent(event);
}
private void emitYieldedEvent(Command command) {
var event = new SchedulerEvent.Yielded(command, RobotController.getTime());
emitEvent(event);
}
private void emitCompletedEvent(Command command) {
var event = new SchedulerEvent.Completed(command, RobotController.getTime());
emitEvent(event);
}
private void emitCompletedWithErrorEvent(Command command, Throwable error) {
var event = new SchedulerEvent.CompletedWithError(command, error, RobotController.getTime());
emitEvent(event);
}
private void emitCanceledEvent(Command command) {
var event = new SchedulerEvent.Canceled(command, RobotController.getTime());
emitEvent(event);
}
private void emitInterruptedEvent(Command command, Command interrupter) {
var event = new SchedulerEvent.Interrupted(command, interrupter, RobotController.getTime());
emitEvent(event);
}
/**
* Adds a listener to handle events that are emitted by the scheduler. Events are emitted when
* certain actions are taken by user code or by internal processing logic in the scheduler.
* Listeners should take care to be quick, simple, and not schedule or cancel commands, as that
* may cause inconsistent scheduler behavior or even cause a program crash.
*
* <p>Listeners are primarily expected to be for data logging and telemetry. In particular, a
* one-shot command (one that never calls {@link Coroutine#yield()}) will never appear in the
* standard protobuf telemetry because it is scheduled, runs, and finishes all in a single
* scheduler cycle. However, {@link SchedulerEvent.Scheduled},{@link SchedulerEvent.Mounted}, and
* {@link SchedulerEvent.Completed} events will be emitted corresponding to those actions, and
* user code can listen for and log such events.
*
* @param listener The listener to add. Cannot be null.
* @throws NullPointerException if given a null listener
*/
public void addEventListener(Consumer<? super SchedulerEvent> listener) {
ErrorMessages.requireNonNullParam(listener, "listener", "addEventListener");
m_eventListeners.add(listener);
}
private void emitEvent(SchedulerEvent event) {
// TODO: Prevent listeners from interacting with the scheduler.
// Scheduling or canceling commands while the scheduler is processing will probably cause
// bugs in user code or even a program crash.
for (var listener : m_eventListeners) {
listener.accept(event);
}
}
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import edu.wpi.first.wpilibj.RobotController;
import java.util.function.Consumer;
/**
* An event that occurs during scheduler processing. This can range from {@link Scheduled a command
* being scheduled} by a trigger or manual call to {@link Scheduler#schedule(Command)} to {@link
* Interrupted a command being interrupted by another}. Event listeners can be registered with a
* scheduler via {@link Scheduler#addEventListener(Consumer)}. All events have a timestamp to
* indicate when the event occurred.
*/
public sealed interface SchedulerEvent {
/**
* The timestamp for when the event occurred. Measured in microseconds since some arbitrary start
* time.
*
* @return The event timestamp.
* @see RobotController#getTime()
*/
long timestampMicros();
/**
* An event marking when a command is scheduled in {@link Scheduler#schedule(Command)}.
*
* @param command The command that was scheduled
* @param timestampMicros When the command was scheduled
*/
record Scheduled(Command command, long timestampMicros) implements SchedulerEvent {}
/**
* An event marking when a command starts running.
*
* @param command The command that started
* @param timestampMicros When the command started
*/
record Mounted(Command command, long timestampMicros) implements SchedulerEvent {}
/**
* An event marking when a command yielded control with {@link Coroutine#yield()}.
*
* @param command The command that yielded
* @param timestampMicros When the command yielded
*/
record Yielded(Command command, long timestampMicros) implements SchedulerEvent {}
/**
* An event marking when a command completed naturally.
*
* @param command The command that completed
* @param timestampMicros When the command completed
*/
record Completed(Command command, long timestampMicros) implements SchedulerEvent {}
/**
* An event marking when a command threw or encountered an unhanded exception.
*
* @param command The command that encountered the error
* @param error The unhandled error
* @param timestampMicros When the error occurred
*/
record CompletedWithError(Command command, Throwable error, long timestampMicros)
implements SchedulerEvent {}
/**
* An event marking when a command was canceled. If the command was canceled because it was
* interrupted by another command, an {@link Interrupted} will be emitted immediately prior to the
* cancellation event.
*
* @param command The command that was canceled
* @param timestampMicros When the command was canceled
*/
record Canceled(Command command, long timestampMicros) implements SchedulerEvent {}
/**
* An event marking when a command was interrupted by another. Typically followed by an {@link
* Canceled} event.
*
* @param command The command that was interrupted
* @param interrupter The interrupting command
* @param timestampMicros When the command was interrupted
*/
record Interrupted(Command command, Command interrupter, long timestampMicros)
implements SchedulerEvent {}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A sequence of commands that run one after another. Each successive command only starts after its
* predecessor completes execution. The priority of a sequence is equal to the highest priority of
* any of its commands. If all commands in the sequence are able to run while the robot is disabled,
* then the sequence itself will be allowed to run while the robot is disabled.
*
* <p>The sequence will <i>own</i> all mechanisms required by all commands in the sequence for the
* entire duration of the sequence. This means that a mechanism owned by one command in the
* sequence, but not by a later one, will be <i>uncommanded</i> while that later command executes.
*/
public final class SequentialGroup implements Command {
private final String m_name;
private final List<Command> m_commands = new ArrayList<>();
private final Set<Mechanism> m_requirements = new HashSet<>();
private final int m_priority;
/**
* Creates a new command sequence.
*
* @param name the name of the sequence
* @param commands the commands to execute within the sequence
*/
SequentialGroup(String name, List<Command> commands) {
requireNonNullParam(name, "name", "SequentialGroup");
requireNonNullParam(commands, "commands", "SequentialGroup");
for (int i = 0; i < commands.size(); i++) {
requireNonNullParam(commands.get(i), "commands[" + i + "]", "SequentialGroup");
}
m_name = name;
m_commands.addAll(commands);
for (var command : commands) {
m_requirements.addAll(command.requirements());
}
m_priority =
commands.stream().mapToInt(Command::priority).max().orElse(Command.DEFAULT_PRIORITY);
}
@Override
public void run(Coroutine coroutine) {
for (var command : m_commands) {
coroutine.await(command);
}
}
@Override
public String name() {
return m_name;
}
@Override
public Set<Mechanism> requirements() {
return m_requirements;
}
@Override
public int priority() {
return m_priority;
}
@Override
public String toString() {
return "SequentialGroup[name=" + m_name + "]";
}
}

View File

@@ -0,0 +1,106 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import org.wpilib.annotation.NoDiscard;
/**
* A builder class to configure and then create a {@link SequentialGroup}. Like {@link
* StagedCommandBuilder}, the final command is created by calling the terminal {@link
* #named(String)} method, or with an automatically generated name using {@link
* #withAutomaticName()}.
*/
@NoDiscard
public class SequentialGroupBuilder {
private final List<Command> m_steps = new ArrayList<>();
private BooleanSupplier m_endCondition;
/**
* Creates new SequentialGroupBuilder. The builder will have no commands and have no preapplied
* configuration options. Use {@link #andThen(Command)} to add commands to the sequence.
*/
public SequentialGroupBuilder() {}
/**
* Adds a command to the sequence.
*
* @param next The next command in the sequence
* @return The builder object, for chaining
*/
public SequentialGroupBuilder andThen(Command next) {
requireNonNullParam(next, "next", "SequentialGroupBuilder.andThen");
m_steps.add(next);
return this;
}
/**
* Adds commands to the sequence. Commands will be added to the sequence in the order they are
* passed to this method.
*
* @param nextCommands The next commands in the sequence
* @return The builder object, for chaining
*/
public SequentialGroupBuilder andThen(Command... nextCommands) {
requireNonNullParam(nextCommands, "nextCommands", "SequentialGroupBuilder.andThen");
for (int i = 0; i < nextCommands.length; i++) {
requireNonNullParam(
nextCommands[i], "nextCommands[" + i + "]", "SequentialGroupBuilder.andThen");
}
m_steps.addAll(Arrays.asList(nextCommands));
return this;
}
/**
* Adds an end condition to the command group. If this condition is met before all required
* commands have completed, the group will exit early. If multiple end conditions are added (e.g.
* {@code .until(() -> conditionA()).until(() -> conditionB())}), then the last end condition
* added will be used and any previously configured condition will be overridden.
*
* @param endCondition The end condition for the group
* @return The builder object, for chaining
*/
public SequentialGroupBuilder until(BooleanSupplier endCondition) {
m_endCondition = endCondition;
return this;
}
/**
* Creates the sequence command, giving it the specified name.
*
* @param name The name of the sequence command
* @return The built command
*/
public Command named(String name) {
var seq = new SequentialGroup(name, m_steps);
if (m_endCondition != null) {
// No custom end condition, return the group as is
return seq;
}
// We have a custom end condition, so we need to wrap the group in a race
return new ParallelGroupBuilder()
.optional(seq, Command.waitUntil(m_endCondition).named("Until Condition"))
.named(name);
}
/**
* Creates the sequence command, giving it an automatically generated name based on the commands
* in the sequence.
*
* @return The built command
*/
public Command withAutomaticName() {
return named(m_steps.stream().map(Command::name).collect(Collectors.joining(" -> ")));
}
}

View File

@@ -0,0 +1,299 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import org.wpilib.annotation.NoDiscard;
/**
* A builder class for commands. Command configuration is done in stages, where later stages have
* different configuration options than earlier stages. Commands may only be created after going
* through every stage, enforcing the presence of required options. All commands <i>must</i> have a
* set of requirements (which may be empty), a name, and an implementation. The builder stages are
* defined such that the final stage that creates a command can only be reached after going through
* the earlier stages to configure those required options.
*
* <p>Example usage:
*
* <pre>{@code
* StagedCommandBuilder start = new StagedCommandBuilder();
* NeedsExecutionBuilderStage withRequirements = start.requiring(mechanism1, mechanism2);
* NeedsNameBuilderStage withExecution = withRequirements.executing(coroutine -> ...);
* Command exampleCommand = withExecution.named("Example Command");
* }</pre>
*
* <p>Because every method on the builders returns a builder object, these calls can be chained to
* cut down on verbosity and make the code easier to read. This is the recommended style:
*
* <pre>{@code
* Command exampleCommand =
* new StagedCommandBuilder()
* .requiring(mechanism1, mechanism2)
* .executing(coroutine -> ...)
* .named("Example Command");
* }</pre>
*
* <p>And can be cut down even further by using helper methods provided by the library:
*
* <pre>{@code
* Command exampleCommand =
* Command
* .requiring(mechanism1, mechanism2)
* .executing(coroutine -> ...)
* .named("Example Command");
* }</pre>
*/
@NoDiscard
public final class StagedCommandBuilder {
private final Set<Mechanism> m_requirements = new HashSet<>();
private Consumer<Coroutine> m_impl;
private Runnable m_onCancel = () -> {};
private String m_name;
private int m_priority = Command.DEFAULT_PRIORITY;
private BooleanSupplier m_endCondition;
private Command m_builtCommand;
// Using internal anonymous classes to implement the staged builder APIs, but all backed by the
// state of the enclosing StagedCommandBuilder object
private final NeedsExecutionBuilderStage m_needsExecutionView =
new NeedsExecutionBuilderStage() {
@Override
public NeedsExecutionBuilderStage requiring(Mechanism requirement) {
throwIfAlreadyBuilt();
requireNonNullParam(requirement, "requirement", "StagedCommandBuilder.requiring");
m_requirements.add(requirement);
return this;
}
@Override
public NeedsExecutionBuilderStage requiring(Mechanism requirement, Mechanism... extra) {
throwIfAlreadyBuilt();
requireNonNullParam(requirement, "requirement", "StagedCommandBuilder.requiring");
requireNonNullParam(extra, "extra", "StagedCommandBuilder.requiring");
for (int i = 0; i < extra.length; i++) {
requireNonNullParam(extra[i], "extra[" + i + "]", "StagedCommandBuilder.requiring");
}
m_requirements.add(requirement);
m_requirements.addAll(Arrays.asList(extra));
return this;
}
@Override
public NeedsExecutionBuilderStage requiring(Collection<Mechanism> requirements) {
throwIfAlreadyBuilt();
requireNonNullParam(requirements, "requirements", "StagedCommandBuilder.requiring");
int i = 0;
for (Mechanism requirement : requirements) {
requireNonNullParam(
requirement, "requirements[" + i + "]", "StagedCommandBuilder.requiring");
i++;
}
m_requirements.addAll(requirements);
return this;
}
@Override
public NeedsNameBuilderStage executing(Consumer<Coroutine> impl) {
throwIfAlreadyBuilt();
requireNonNullParam(impl, "impl", "StagedCommandBuilder.executing");
m_impl = impl;
return m_needsNameView;
}
};
private final NeedsNameBuilderStage m_needsNameView =
new NeedsNameBuilderStage() {
@Override
public NeedsNameBuilderStage whenCanceled(Runnable onCancel) {
throwIfAlreadyBuilt();
m_onCancel = onCancel;
return this;
}
@Override
public NeedsNameBuilderStage withPriority(int priority) {
throwIfAlreadyBuilt();
m_priority = priority;
return this;
}
@Override
public NeedsNameBuilderStage until(BooleanSupplier endCondition) {
throwIfAlreadyBuilt();
m_endCondition = endCondition; // allowed to be null
return this;
}
@Override
public Command named(String name) {
throwIfAlreadyBuilt();
requireNonNullParam(name, "name", "StagedCommandBuilder.withName");
m_name = name;
var command = new BuilderBackedCommand(StagedCommandBuilder.this);
if (m_endCondition == null) {
// No custom end condition, just return the raw command
m_builtCommand = command;
} else {
// A custom end condition is implemented as a race group, since we cannot modify the
// command body to inject the end condition.
m_builtCommand =
new ParallelGroupBuilder().requiring(command).until(m_endCondition).named(m_name);
}
return m_builtCommand;
}
};
private static final class BuilderBackedCommand implements Command {
private static final Runnable kNoOp = () -> {};
private final Set<Mechanism> m_requirements;
private final Consumer<Coroutine> m_impl;
private final Runnable m_onCancel;
private final String m_name;
private final int m_priority;
private BuilderBackedCommand(StagedCommandBuilder builder) {
// Copy builder fields into the command so the builder object can be garbage collected
m_requirements = new HashSet<>(builder.m_requirements);
m_impl = builder.m_impl;
m_onCancel = Objects.requireNonNullElse(builder.m_onCancel, kNoOp);
m_name = builder.m_name;
m_priority = builder.m_priority;
}
@Override
public void run(Coroutine coroutine) {
m_impl.accept(coroutine);
}
@Override
public void onCancel() {
m_onCancel.run();
}
@Override
public String name() {
return m_name;
}
@Override
public Set<Mechanism> requirements() {
return m_requirements;
}
@Override
public int priority() {
return m_priority;
}
@Override
public String toString() {
return name();
}
}
/**
* Creates a new command builder. All required options must be set on each stage before a command
* is able to be created. Attempting to create a command without setting all required options will
* result in a compilation error.
*/
public StagedCommandBuilder() {}
/**
* Explicitly marks the command as requiring no mechanisms. Unless overridden later with {@link
* NeedsExecutionBuilderStage#requiring(Mechanism)} or a similar method, the built command will
* not have ownership over any mechanisms when it runs. Use this for commands that don't need to
* own a mechanism, such as a gyro zeroing command, that does some kind of cleanup task without
* needing to control something.
*
* @return A builder object that can be used to further configure the command.
*/
public NeedsExecutionBuilderStage noRequirements() {
throwIfAlreadyBuilt();
return m_needsExecutionView;
}
/**
* Marks the command as requiring one or more mechanisms. If only a single mechanism is required,
* prefer a factory function like {@link Mechanism#run(Consumer)} or similar - it will
* automatically require the mechanism, instead of it needing to be explicitly specified.
*
* @param requirement The first required mechanism. Cannot be null.
* @param extra Any optional extra required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return A builder object that can be used to further configure the command.
*/
public NeedsExecutionBuilderStage requiring(Mechanism requirement, Mechanism... extra) {
throwIfAlreadyBuilt();
requireNonNullParam(requirement, "requirement", "StagedCommandBuilder.requiring");
requireNonNullParam(extra, "extra", "StagedCommandBuilder.requiring");
for (int i = 0; i < extra.length; i++) {
requireNonNullParam(extra[i], "extra[" + i + "]", "StagedCommandBuilder.requiring");
}
m_requirements.add(requirement);
m_requirements.addAll(Arrays.asList(extra));
return m_needsExecutionView;
}
/**
* Marks the command as requiring zero or more mechanisms. If only a single mechanism is required,
* prefer a factory function like {@link Mechanism#run(Consumer)} or similar - it will
* automatically require the mechanism, instead of it needing to be explicitly specified.
*
* @param requirements A collection of required mechanisms. May be empty, but cannot be null or
* contain null values.
* @return A builder object that can be used to further configure the command.
*/
public NeedsExecutionBuilderStage requiring(Collection<Mechanism> requirements) {
throwIfAlreadyBuilt();
requireNonNullParam(requirements, "requirements", "StagedCommandBuilder.requiring");
int i = 0;
for (var mechanism : requirements) {
requireNonNullParam(mechanism, "requirements[" + i + "]", "StagedCommandBuilder.requiring");
i++;
}
m_requirements.addAll(requirements);
return m_needsExecutionView;
}
// Prevent builders from being mutated after command creation
// Weird things could happen like changing requirements, priority level, or even the command
// implementation itself if we didn't prohibit it.
private void throwIfAlreadyBuilt() {
if (m_builtCommand != null) {
throw new IllegalStateException("Command builders cannot be reused");
}
}
}

View File

@@ -0,0 +1,368 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package org.wpilib.commands3;
import static edu.wpi.first.units.Units.Seconds;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.math.filter.Debouncer;
import edu.wpi.first.units.measure.Time;
import edu.wpi.first.wpilibj.event.EventLoop;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
/**
* Triggers allow users to specify conditions for when commands should run. Triggers can be set up
* to read from joystick and controller buttons (eg {@link
* org.wpilib.commands3.button.CommandXboxController#x()}) or be customized to read sensor values or
* any other arbitrary true/false condition.
*
* <p>It is very easy to link a button to a command. For instance, you could link the trigger button
* of a joystick to a "score" command.
*
* <p>Triggers can easily be composed for advanced functionality using the {@link
* #and(BooleanSupplier)}, {@link #or(BooleanSupplier)}, {@link #negate()} operators.
*
* <p>Trigger bindings created inside a running command will only be active while that command is
* running. This is useful for defining trigger-based behavior only in a certain scope and avoids
* needing to create dozens of global triggers. Any commands scheduled by these triggers will be
* canceled when the enclosing command exits.
*
* <pre>{@code
* Command shootWhileAiming = Command.noRequirements().executing(co -> {
* turret.atTarget.onTrue(shooter.shootOnce());
* co.await(turret.lockOnGoal());
* }).named("Shoot While Aiming");
* controller.rightBumper().whileTrue(shootWhileAiming);
* }</pre>
*/
public class Trigger implements BooleanSupplier {
private final BooleanSupplier m_condition;
private final EventLoop m_loop;
private final Scheduler m_scheduler;
private Signal m_previousSignal;
private final Map<BindingType, List<Binding>> m_bindings = new EnumMap<>(BindingType.class);
private final Runnable m_eventLoopCallback = this::poll;
private boolean m_isBoundToEventLoop; // used for lazily binding to the event loop
/**
* Represents the state of a signal: high or low. Used instead of a boolean for nullity on the
* first run, when the previous signal value is undefined and unknown.
*/
private enum Signal {
/** The signal is high. */
HIGH,
/** The signal is low. */
LOW
}
/**
* Creates a new trigger based on the given condition. Polled by the scheduler's {@link
* Scheduler#getDefaultEventLoop() default event loop}.
*
* @param scheduler The scheduler that should execute triggered commands.
* @param condition the condition represented by this trigger
*/
public Trigger(Scheduler scheduler, BooleanSupplier condition) {
m_scheduler = requireNonNullParam(scheduler, "scheduler", "Trigger");
m_loop = scheduler.getDefaultEventLoop();
m_condition = requireNonNullParam(condition, "condition", "Trigger");
}
/**
* Creates a new trigger based on the given condition. Triggered commands are executed by the
* {@link Scheduler#getDefault() default scheduler}.
*
* <p>Polled by the default scheduler button loop.
*
* @param condition the condition represented by this trigger
*/
public Trigger(BooleanSupplier condition) {
this(Scheduler.getDefault(), condition);
}
/**
* Creates a new trigger based on the given condition.
*
* @param scheduler The scheduler that should execute triggered commands.
* @param loop The event loop to poll the trigger.
* @param condition the condition represented by this trigger
*/
public Trigger(Scheduler scheduler, EventLoop loop, BooleanSupplier condition) {
m_scheduler = requireNonNullParam(scheduler, "scheduler", "Trigger");
m_loop = requireNonNullParam(loop, "loop", "Trigger");
m_condition = requireNonNullParam(condition, "condition", "Trigger");
}
/**
* Starts the given command whenever the condition changes from `false` to `true`.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger onTrue(Command command) {
requireNonNullParam(command, "command", "onTrue");
addBinding(BindingType.SCHEDULE_ON_RISING_EDGE, command);
return this;
}
/**
* Starts the given command whenever the condition changes from `true` to `false`.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger onFalse(Command command) {
requireNonNullParam(command, "command", "onFalse");
addBinding(BindingType.SCHEDULE_ON_FALLING_EDGE, command);
return this;
}
/**
* Starts the given command when the condition changes to `true` and cancels it when the condition
* changes to `false`.
*
* <p>Doesn't re-start the command if it ends while the condition is still `true`.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger whileTrue(Command command) {
requireNonNullParam(command, "command", "whileTrue");
addBinding(BindingType.RUN_WHILE_HIGH, command);
return this;
}
/**
* Starts the given command when the condition changes to `false` and cancels it when the
* condition changes to `true`.
*
* <p>Doesn't re-start the command if it ends while the condition is still `false`.
*
* @param command the command to start
* @return this trigger, so calls can be chained
*/
public Trigger whileFalse(Command command) {
requireNonNullParam(command, "command", "whileFalse");
addBinding(BindingType.RUN_WHILE_LOW, command);
return this;
}
/**
* Toggles a command when the condition changes from `false` to `true`.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnTrue(Command command) {
requireNonNullParam(command, "command", "toggleOnTrue");
addBinding(BindingType.TOGGLE_ON_RISING_EDGE, command);
return this;
}
/**
* Toggles a command when the condition changes from `true` to `false`.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained
*/
public Trigger toggleOnFalse(Command command) {
requireNonNullParam(command, "command", "toggleOnFalse");
addBinding(BindingType.TOGGLE_ON_FALLING_EDGE, command);
return this;
}
@Override
public boolean getAsBoolean() {
return m_condition.getAsBoolean();
}
/**
* Composes two triggers with logical AND.
*
* @param trigger the condition to compose with
* @return A trigger which is active when both component triggers are active.
*/
public Trigger and(BooleanSupplier trigger) {
return new Trigger(
m_scheduler, m_loop, () -> m_condition.getAsBoolean() && trigger.getAsBoolean());
}
/**
* Composes two triggers with logical OR.
*
* @param trigger the condition to compose with
* @return A trigger which is active when either component trigger is active.
*/
public Trigger or(BooleanSupplier trigger) {
return new Trigger(
m_scheduler, m_loop, () -> m_condition.getAsBoolean() || trigger.getAsBoolean());
}
/**
* Creates a new trigger that is active when this trigger is inactive, i.e. that acts as the
* negation of this trigger.
*
* @return the negated trigger
*/
public Trigger negate() {
return new Trigger(m_scheduler, m_loop, () -> !m_condition.getAsBoolean());
}
/**
* Creates a new debounced trigger from this trigger - it will become active when this trigger has
* been active for longer than the specified period.
*
* @param duration The debounce period.
* @return The debounced trigger (rising edges debounced only)
*/
public Trigger debounce(Time duration) {
return debounce(duration, Debouncer.DebounceType.kRising);
}
/**
* Creates a new debounced trigger from this trigger - it will become active when this trigger has
* been active for longer than the specified period.
*
* @param duration The debounce period.
* @param type The debounce type.
* @return The debounced trigger.
*/
public Trigger debounce(Time duration, Debouncer.DebounceType type) {
var debouncer = new Debouncer(duration.in(Seconds), type);
return new Trigger(m_scheduler, m_loop, () -> debouncer.calculate(m_condition.getAsBoolean()));
}
private void poll() {
// Clear bindings that no longer need to run
// This should always be checked, regardless of signal change, since bindings may be scoped
// and those scopes may become inactive.
clearStaleBindings();
var signal = readSignal();
if (signal == m_previousSignal) {
// No change in the signal. Nothing to do
return;
}
if (signal == Signal.HIGH) {
// Signal is now high when it wasn't before - a rising edge
scheduleBindings(BindingType.SCHEDULE_ON_RISING_EDGE);
scheduleBindings(BindingType.RUN_WHILE_HIGH);
cancelBindings(BindingType.RUN_WHILE_LOW);
toggleBindings(BindingType.TOGGLE_ON_RISING_EDGE);
}
if (signal == Signal.LOW) {
// Signal is now low when it wasn't before - a falling edge
scheduleBindings(BindingType.SCHEDULE_ON_FALLING_EDGE);
scheduleBindings(BindingType.RUN_WHILE_LOW);
cancelBindings(BindingType.RUN_WHILE_HIGH);
toggleBindings(BindingType.TOGGLE_ON_FALLING_EDGE);
}
m_previousSignal = signal;
}
private Signal readSignal() {
if (m_condition.getAsBoolean()) {
return Signal.HIGH;
} else {
return Signal.LOW;
}
}
/** Removes bindings in inactive scopes. Running commands tied to those bindings are canceled. */
private void clearStaleBindings() {
m_bindings.forEach(
(_bindingType, bindings) -> {
for (var iterator = bindings.iterator(); iterator.hasNext(); ) {
var binding = iterator.next();
if (binding.scope().active()) {
// This binding's scope is still active, leave it running
continue;
}
// The scope is no long active. Remove the binding and immediately cancel its command.
iterator.remove();
m_scheduler.cancel(binding.command());
}
});
}
/**
* Schedules all commands bound to the given binding type.
*
* @param bindingType the binding type to schedule
*/
private void scheduleBindings(BindingType bindingType) {
m_bindings.getOrDefault(bindingType, List.of()).forEach(m_scheduler::schedule);
}
/**
* Cancels all commands bound to the given binding type.
*
* @param bindingType the binding type to cancel
*/
private void cancelBindings(BindingType bindingType) {
m_bindings
.getOrDefault(bindingType, List.of())
.forEach(binding -> m_scheduler.cancel(binding.command()));
}
/**
* Toggles all commands bound to the given binding type. If a command is currently scheduled or
* running, it will be canceled; otherwise, it will be scheduled.
*
* @param bindingType the binding type to cancel
*/
private void toggleBindings(BindingType bindingType) {
m_bindings
.getOrDefault(bindingType, List.of())
.forEach(
binding -> {
var command = binding.command();
if (m_scheduler.isScheduledOrRunning(command)) {
m_scheduler.cancel(command);
} else {
m_scheduler.schedule(binding);
}
});
}
// package-private for testing
void addBinding(BindingScope scope, BindingType bindingType, Command command) {
// Note: we use a throwable here instead of Thread.currentThread().getStackTrace() for easier
// stack frame filtering and modification.
m_bindings
.computeIfAbsent(bindingType, _k -> new ArrayList<>())
.add(new Binding(scope, bindingType, command, new Throwable().getStackTrace()));
if (!m_isBoundToEventLoop) {
m_loop.bind(m_eventLoopCallback);
m_isBoundToEventLoop = true;
}
}
private void addBinding(BindingType bindingType, Command command) {
BindingScope scope =
switch (m_scheduler.currentCommand()) {
case Command c -> {
// A command is creating a binding - make it scoped to that specific command
yield BindingScope.forCommand(m_scheduler, c);
}
case null -> {
// Creating a binding outside a command - it's global in scope
yield BindingScope.global();
}
};
addBinding(scope, bindingType, command);
}
}

Some files were not shown because too many files have changed in this diff Show More