Compare commits

...

73 Commits

Author SHA1 Message Date
Peter Johnson
cf8faa9e67 [wpilib] Update values on controllable sendables (#4667) 2022-11-18 23:50:41 -08:00
Peter Johnson
5ec067c1f8 [ntcore] Implement keep duplicates pub/sub flag (#4666)
Also don't save duplicate NT sets to data log (unless publish keep duplicates flag is set).
2022-11-18 23:50:08 -08:00
Peter Johnson
e962fd2916 [ntcore] Allow numeric-compatible value sets (#4620)
Also fix entry publishing behavior to allow numerically compatible set
default publish following a subscribe.
2022-11-18 22:46:24 -08:00
Ryan Blue
88bd67e7de [ci] Update clang repositories to jammy (#4665) 2022-11-18 20:42:55 -08:00
Jordan McMichael
902e8686d3 [wpimath] Rework odometry APIs to improve feature parity (#4645)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-18 20:42:00 -08:00
Thad House
e2d49181da Update to native utils 2023.8.0 (#4664) 2022-11-18 19:01:26 -08:00
Peter Johnson
149bac55b1 [cscore] Add Arducam OV9281 exposure quirk (#4663)
Reports exposure range as 1-5000 but real range is 1-75.
2022-11-18 14:15:30 -08:00
amquake
88f7a3ccb9 [wpimath] Fix Pose relativeTo documentation (#4661) 2022-11-18 14:07:50 -08:00
sciencewhiz
8acce443f0 [examples] Fix swerve examples to use getDistance for turning encoder (#4652) 2022-11-18 14:06:21 -08:00
Peter Johnson
295a1f8f3b [ntcore] Fix WaitForListenerQueue (#4662) 2022-11-18 14:01:52 -08:00
Dustin Spicuzza
388e7a4265 [ntcore] Provide mechanism to reset internals of NT instance (#4653) 2022-11-18 10:21:05 -08:00
Tyler Veness
13aceea8dc [apriltag] Fix FieldDimensions argument order (#4659) 2022-11-17 22:15:55 -08:00
Ryan Blue
c203f3f0a9 [apriltag] Fix documentation for AprilTagFieldLayout (#4657) 2022-11-17 19:40:51 -08:00
Thad House
f54d495c90 Fix non initialized hal functionality during motor safety init (#4658) 2022-11-17 18:54:45 -08:00
Starlight220
e6392a1570 [cmd] Change factories return type to CommandBase (#4655) 2022-11-17 15:44:16 -08:00
PJ Reiniger
53904e7cf4 [apriltag] Split AprilTag functionality to a separate library (#4578)
Add AprilTagFieldLayout JSON file and move class to edu.wpi.first.apriltag.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-11-17 14:29:29 -08:00
Drew Williams
2e88a496c2 [wpimath] Add support for swerve joystick normalization (#4516) 2022-11-17 10:53:49 -08:00
Jordan McMichael
ce4c45df13 [wpimath] Rework function signatures for Pose Estimation / Odometry (#4642)
Forcing the use of SwerveModulePostition[] and wpi::array<SwerveModulePosition, len> in place of variadic function signatures.
2022-11-17 10:36:33 -08:00
sciencewhiz
0401597d3b [readme] Add wpinet to MavenArtifacts.md (#4651)
Fixes #4639
2022-11-17 10:35:27 -08:00
Jordan McMichael
2e5f9e45bb [wpimath] Remove encoder reset comments on Swerve, Mecanum Odometry and Pose Estimation (#4643)
In both of these cases, encoder resets are not required for normal use.
2022-11-16 15:20:05 -08:00
sciencewhiz
e4b5795fc7 [docs] Disable Doxygen for memory to fix search (#4636) 2022-11-14 20:19:44 -08:00
Peter Johnson
03d0ea188c [build] cmake: Add missing wpinet to installed config file (#4637) 2022-11-14 20:19:13 -08:00
Thad House
3082bd236b [build] Move version file to its own source set (#4638) 2022-11-14 20:19:00 -08:00
Thad House
b7ca860417 [build] Use build cache for sign step (#4635)
The signing step does not get passed the username and password to the server, so it will just read from the build cache. Then to make sure the tasks are all updated correctly, use if developerid exists as a property to all sign tasks, so it will see the new variable value, and relink. Additionally only update the archives during signing, which will speed up signing.
2022-11-14 19:23:13 -08:00
Starlight220
64838e6367 [commands] Remove unsafe default command isFinished check (#4411) 2022-11-14 14:28:30 -08:00
Peter Johnson
1269d2b901 [myRobot] Disable spotbugs (#4565) 2022-11-14 14:26:41 -08:00
Tyler Veness
14d8506b72 [wpimath] Fix units docs for LinearSystemId::IdentifyDrivetrainSystem() (#4600) 2022-11-14 14:26:16 -08:00
ohowe
d1d458db2b [wpimath] Constrain Rotation2d range to -pi to pi (#4611)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-14 14:25:15 -08:00
Tyler Veness
f656e99245 [readme] Add links to development build documentation (#4481)
* Add links to development build documentation
* Use GitHub dev docs links in readme badges
2022-11-14 14:23:47 -08:00
Starlight220
6dd937cef7 [commands] Fix Trigger API docs (NFC) (#4599) 2022-11-14 14:21:35 -08:00
Starlight220
49047c85b9 [commands] Report error on C++ CommandPtr use-after-move (#4575) 2022-11-14 14:20:52 -08:00
Tyler Veness
d07267fed1 [ci] Upgrade containers to Ubuntu 22.04 and remove libclang installation (#4633) 2022-11-14 14:20:08 -08:00
Thad House
b53ce1d3f0 [build, wpiutil] Switch macos to universal binaries (#4628) 2022-11-14 10:36:33 -08:00
Ege Akman
5a320c326b [upstream_util, wpiutil] Refactor python scripts (#4614)
Co-authored-by: Sourcery AI <>
Co-authored-by: Vasista Vovveti <vasistavovveti@gmail.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2022-11-13 23:11:54 -08:00
Peter Johnson
c4e526d315 [glass] Fix NT Mechanism2D (#4626) 2022-11-13 23:09:23 -08:00
Ryan Blue
d122e4254f [ci] Run spotlessApply after wpiformat in comment command (#4623) 2022-11-13 14:07:26 -08:00
Peter Johnson
5a1e7ea036 [wpilibj] FieldObject2d: Add null check to close() (#4619) 2022-11-12 16:51:41 -08:00
Peter Johnson
179f569113 [ntcore] Notify locally on SetDefault (#4617)
This is necessary for the simulation GUI to pick up default publishes.
2022-11-12 06:33:10 -08:00
Peter Johnson
b0f6dc199d [wpilibc] ShuffleboardComponent.WithProperties: Update type (#4615)
ShuffleboardComponentBase::m_properties is now a StringMap<nt::Value>.
2022-11-11 15:08:05 -08:00
Tyler Veness
7836f661cd [wpimath] Add missing open curly brace to units/base.h (#4613) 2022-11-11 13:06:42 -08:00
CarloWoolsey
dbcc1de37f [wpimath] Add DifferentialDriveFeedforward classes which wrap LinearPlantInversionFeedforward (#4598)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-11-10 16:54:51 -08:00
Cory
93890c528b [wpimath] Add additional angular acceleration units (#4610)
Added Turns per second squared
Added Revolutions per minute squared
Added Revolutions per minute per second
2022-11-10 16:47:55 -08:00
Tyler Veness
3d8d5936f9 [wpimath] Add macro for disabling units fmt support (#4609) 2022-11-10 14:12:07 -08:00
Tyler Veness
2b04159dec [wpimath] Update units/base.h license header (#4608) 2022-11-10 14:11:44 -08:00
Thad House
2764004fad [wpinet] Fix incorrect jni definitions (#4605)
Also re-enables the check task that would have caught this.
2022-11-10 09:42:02 -08:00
Thad House
85f1bb8f2b [wpiutil] Reenable jni check task (#4606)
New option was added to JNI plugin to allow skipping specific symbols. This let us generally reenable the check task in wpiutil.
2022-11-10 09:33:26 -08:00
ohowe
231ae2c353 [glass] Plot: Fix Y-axis not being saved (#4594) 2022-11-08 21:33:02 -08:00
Tyler Veness
e92b6dd5f9 [wpilib] Fix AprilTagFieldLayout JSON property name typos (#4597) 2022-11-08 13:27:21 -08:00
Thad House
2a8e0e1cc8 Update all dependencies that use grgit (#4596) 2022-11-08 10:20:16 -08:00
Starlight220
7d06e517e9 [commands] Move SelectCommand factory impl to header (#4581)
It's templated, so it needs to be in the header.
2022-11-07 15:49:36 -08:00
Tyler Veness
323524fed6 [wpimath] Remove deprecated units/units.h header (#4572) 2022-11-07 10:18:36 -08:00
Starlight220
d426873ed1 [commands] Add missing PS4 triangle methods (#4576) 2022-11-07 10:15:54 -08:00
PJ Reiniger
5be5869b2f [apriltags] Use map as internal data model (#4577)
This leaves the file format as a list, but internally will transform the collection of tags into a map on de/serialization. The serialization will probably happen once on startup, but the tag lookup can happen 100s of times a second. This honestly probably doesn't make too much of a performance hit since N is small, but this is a simple O(n) -> O(1) change for lookups.
2022-11-07 10:09:06 -08:00
Griffin Della Grotte
b1b4c1e9e7 [wpimath] Fix Pose3d transformBy rotation type (#4545)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-07 09:57:33 -08:00
Dustin Spicuzza
a4054d702f [commands] Allow composing two triggers directly (#4580)
For backwards compatibility reasons.
2022-11-07 09:55:28 -08:00
Dustin Spicuzza
0190301e09 [wpilibc] Explicitly mark EventLoop as non-copyable/non-movable (#4579)
It's already not movable because m_bindings isn't copyable, but pybind11
isn't able to detect that
2022-11-07 09:30:03 -08:00
Peter Johnson
9d1ce6a6d9 [ntcore] Catch file open error when saving preferences (#4571) 2022-11-05 21:16:39 -07:00
Peter Johnson
5005e2ca04 [ntcore] Change Java event mask to EnumSet (#4564)
Also convert NetworkTableInstance.getNetworkMode() to return EnumSet.
2022-11-04 20:25:37 -07:00
PJ Reiniger
fa44a07938 [upstream-utils][mpack] Add upstream util for mpack (#4500) 2022-11-04 20:03:49 -07:00
Peter Johnson
4ba16db645 [ntcore] Various fixes and cleanups (#4544)
* NetworkTableInstance: set handle to 0 after destroy
* Fix multiple notifications of local values
* Detect mismatch between handles
* Server: fix setting min period when no topics
* Limit maximum number of subscribers/publishers/listeners
   This helps find resource leaks and prevents them from causing excessive
   slowdowns/crashes.  The limit on each is currently set to 512.
* Don't use std::swap in move operation
2022-11-04 20:01:21 -07:00
Thad House
837415abfd [hal] Fix joysticks either crashing or returning 0 (#4570) 2022-11-04 19:03:11 -07:00
amquake
2c20fd0d09 [wpilib] SingleJointedArmSim: Check angle equals limit on wouldHit (#4567) 2022-11-04 17:14:46 -07:00
Alex Ryker
64a7136e08 [wpimath] SwerveDrivePoseEstimator: Restore comment about encoder reset (#4569) 2022-11-04 15:03:49 -07:00
Brennen Puth
b2b473b24a [wpilib] Add AprilTag and AprilTagFieldLayout (#4421)
This is an API for looking up a Pose3d from a tag id, and includes functionality to load that map from a JSON file.

This also adds JSON support to Pose3d, Rotation3d. Translation3d, and Quaternion.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: AMereBagatelle <themerebagatelle@gmail.com>
2022-11-04 09:56:22 -07:00
Thad House
7aab8fa93a [build] Update to Native Utils 2023.6.0 (#4563) 2022-11-03 20:57:04 -07:00
Starlight220
12c2851856 [commands] WrapperCommand: inherit from CommandBase (#4561)
This makes WrapperCommand Sendable.
Only Java had this issue.
2022-11-03 06:27:23 -07:00
Tyler Veness
0da169dd84 [wpimath] Remove template argument from ElevatorFeedforward (#4554) 2022-11-02 22:54:32 -07:00
Tyler Veness
2416827c25 [wpimath] Fix docs for pose estimator local measurement models (#4558) 2022-11-02 22:53:21 -07:00
Michael Jansen
1177a3522e [wpilib] Fix Xbox/PS4 POV sim for port number constructors (#4548) 2022-11-02 22:52:26 -07:00
ohowe
102344e27a [commands] HID classes: Add missing methods, tweak return types (#4557)
- Make return type of getHID reflect the specific class
- Add getX and getY to CommandJoystick
2022-11-02 22:51:53 -07:00
Peter Johnson
1831ef3e19 [wpilib] Fix Shuffleboard SuppliedValueWidget (#4559)
It was creating duplicate publishers.
2022-11-02 22:49:52 -07:00
Starlight220
a9606ce870 [wpilib] Fix Xbox/PS4 POV sim (#4546) 2022-11-02 10:52:15 -07:00
Tyler Veness
6c80d5eab3 [wpimath] Remove unused SymbolExports.h include from units/base.h (#4541) 2022-11-01 17:18:24 -07:00
229 changed files with 4654 additions and 918 deletions

View File

@@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: ubuntu-22.04
name: Linux
container: wpilib/roborio-cross-ubuntu:2023-22.04
flags: ""

View File

@@ -6,7 +6,7 @@ on:
jobs:
wpiformat:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: React Rocket
uses: actions/github-script@v4
@@ -37,16 +37,23 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 11
- name: Install clang-format
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-format-14
- name: Install wpiformat
run: pip3 install wpiformat
- name: Run wpiformat
run: wpiformat -clang 14
- name: Run spotlessApply
run: ./gradlew spotlessApply
- name: Commit
run: |
# Set credentials

View File

@@ -12,7 +12,7 @@ env:
jobs:
publish:
name: "Documentation - Publish"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
concurrency: ci-docs-publish
steps:
@@ -24,8 +24,6 @@ jobs:
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
- name: Set environment variables (Development)
run: |
echo "TARGET_FOLDER=$BASE_PATH/development" >> $GITHUB_ENV

View File

@@ -9,7 +9,7 @@ concurrency:
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/gazebo-ubuntu:22.04
steps:
- uses: actions/checkout@v3

View File

@@ -25,7 +25,7 @@ jobs:
artifact-name: Linux
build-options: "-Ponlylinuxx86-64"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v3
@@ -66,7 +66,6 @@ jobs:
- os: macOS-11
artifact-name: macOS
architecture: x64
build-options: "-Pbuildalldesktop"
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
@@ -112,7 +111,7 @@ jobs:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- name: Sign Libraries with Developer ID
run: ./gradlew build -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
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_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
@@ -123,7 +122,7 @@ jobs:
build-documentation:
name: "Build - Documentation"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
@@ -132,8 +131,6 @@ jobs:
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
@@ -150,7 +147,7 @@ jobs:
combine:
name: Combine
needs: [build-docker, build-host, build-documentation]
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:

View File

@@ -13,7 +13,7 @@ concurrency:
jobs:
wpiformat:
name: "wpiformat"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -29,7 +29,7 @@ jobs:
- name: Install clang-format
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-format-14
- name: Install wpiformat
@@ -56,7 +56,7 @@ jobs:
tidy:
name: "clang-tidy"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3
@@ -73,7 +73,7 @@ jobs:
- name: Install clang-tidy
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-tidy-14 clang-format-14
- name: Install wpiformat
@@ -86,7 +86,7 @@ jobs:
run: wpiformat -clang 14 -no-format -tidy-changed -compile-commands=build/compile_commands/linuxx86-64 -vv
javaformat:
name: "Java format"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/ubuntu-base:22.04
steps:
- uses: actions/checkout@v3
@@ -105,7 +105,7 @@ jobs:
if: ${{ failure() }}
documentation:
name: "Documentation"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
@@ -114,7 +114,5 @@ jobs:
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
- name: Build with Gradle
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}

View File

@@ -25,7 +25,7 @@ jobs:
ctest-env: ""
ctest-flags: ""
name: "${{ matrix.name }}"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3

View File

@@ -13,7 +13,7 @@ concurrency:
jobs:
update:
name: "Update"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -49,6 +49,10 @@ jobs:
run: |
cd upstream_utils
./update_llvm.py
- name: Run update_mpack.py
run: |
cd upstream_utils
./update_mpack.py
- name: Run update_stack_walker.py
run: |
cd upstream_utils

View File

@@ -315,6 +315,7 @@ endif()
if (WITH_WPILIB)
set(WPILIBC_DEP_REPLACE ${WPILIBC_DEP_REPLACE_IMPL})
add_subdirectory(apriltag)
add_subdirectory(wpilibj)
add_subdirectory(wpilibc)
add_subdirectory(wpilibNewCommands)

View File

@@ -8,7 +8,7 @@ This article contains instructions on building projects using a development buil
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
```groovy
wpi.maven.useLocal = false
@@ -46,6 +46,11 @@ wpi.versions.wpilibVersion = '2023.+'
wpi.versions.wpimathVersion = '2023.+'
```
### Development Build Documentation
* C++: https://github.wpilib.org/allwpilib/docs/development/cpp/
* Java: https://github.wpilib.org/allwpilib/docs/development/java/
## Local Build
Building with a local build is very similar to building with a development build. Ensure you have built and published WPILib by following the instructions attached [here](https://github.com/wpilibsuite/allwpilib#building-wpilib). Next, find the ``build.gradle`` file in your robot project and open it. Then, add the following code below the plugin section and replace ``YEAR`` with the year of the local version.

View File

@@ -72,12 +72,16 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* wpigui
* imgui
* ntcore
* wpiutil
* wpimath
* wpiutil
* wpinet
* wpiutil
* ntcore
* wpiutil
* wpinet
* glass/libglass
* wpiutil
* wpimath
@@ -85,6 +89,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* glass/libglassnt
* wpiutil
* wpinet
* ntcore
* wpimath
* wpigui
@@ -94,6 +99,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* halsim
* wpiutil
* wpinet
* ntcore
* wpimath
* wpigui
@@ -102,12 +108,14 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cscore
* opencv
* wpinet
* wpiutil
* cameraserver
* ntcore
* cscore
* opencv
* wpinet
* wpiutil
* wpilibj
@@ -115,6 +123,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cameraserver
* ntcore
* cscore
* wpinet
* wpiutil
* wpilibc
@@ -123,6 +132,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* ntcore
* cscore
* wpimath
* wpinet
* wpiutil
* wpilibNewCommands
@@ -132,6 +142,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* ntcore
* cscore
* wpimath
* wpinet
* wpiutil

View File

@@ -1,8 +1,8 @@
# WPILib Project
![CI](https://github.com/wpilibsuite/allwpilib/workflows/CI/badge.svg)
[![C++ Documentation](https://img.shields.io/badge/documentation-c%2B%2B-blue)](https://first.wpi.edu/wpilib/allwpilib/docs/development/cpp/)
[![Java Documentation](https://img.shields.io/badge/documentation-java-orange)](https://first.wpi.edu/wpilib/allwpilib/docs/development/java/)
[![C++ Documentation](https://img.shields.io/badge/documentation-c%2B%2B-blue)](https://github.wpilib.org/allwpilib/docs/development/cpp/)
[![Java Documentation](https://img.shields.io/badge/documentation-java-orange)](https://github.wpilib.org/allwpilib/docs/development/java/)
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
@@ -33,7 +33,7 @@ Below is a list of instructions that guide you through cloning, building, publis
1. Clone the repository with `git clone https://github.com/wpilibsuite/allwpilib.git`
2. Build the repository with `./gradlew build` or `./gradlew build --build-cache` if you have an internet connection
3. Publish the artifacts locally by running `./gradlew publish`
4. [Update your](OtherVersions.md) `build.gradle` [to use the artifacts](OtherVersions.md)
4. [Update your](DevelopmentBuilds.md) `build.gradle` [to use the artifacts](DevelopmentBuilds.md)
# Building WPILib
@@ -104,7 +104,7 @@ Run with `--build-cache` on the command-line to use the shared [build cache](htt
### Using Development Builds
Please read the documentation available [here](OtherVersions.md)
Please read the documentation available [here](DevelopmentBuilds.md)
### Custom toolchain location

41
apriltag/CMakeLists.txt Normal file
View File

@@ -0,0 +1,41 @@
project(fieldImages)
include(CompileWarnings)
include(GenResources)
if (WITH_JAVA)
find_package(Java REQUIRED)
include(UseJava)
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
set(CMAKE_JAVA_INCLUDE_PATH apriltags.jar ${JACKSON_JARS})
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
add_jar(apriltags_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES} INCLUDE_JARS wpimath_jar OUTPUT_NAME apriltag)
endif()
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltags_resources_src)
file(GLOB_RECURSE apriltags_native_src src/main/native/cpp/*.cpp)
add_library(apriltags ${apriltags_native_src} ${apriltags_resources_src})
set_target_properties(apriltags PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET apriltags PROPERTY FOLDER "libraries")
target_compile_features(apriltags PUBLIC cxx_std_20)
wpilib_target_warnings(apriltags)
target_link_libraries(apriltags wpimath)
target_include_directories(apriltags PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/apriltags>)
if (WITH_TESTS)
wpilib_add_test(apriltags src/test/native/cpp)
target_include_directories(apriltags_test PRIVATE src/test/native/include)
target_link_libraries(apriltags_test apriltags gmock_main)
endif()

77
apriltag/build.gradle Normal file
View File

@@ -0,0 +1,77 @@
apply from: "${rootDir}/shared/resources.gradle"
ext {
nativeName = 'apriltags'
devMain = 'edu.wpi.first.apriltag.DevMain'
def generateTask = createGenerateResourcesTask('main', 'APRILTAGS', 'frc', project)
tasks.withType(CppCompile) {
dependsOn generateTask
}
extraSetup = {
it.sources {
resourcesCpp(CppSourceSet) {
source {
srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib"
include '*.cpp'
}
}
}
}
}
evaluationDependsOn(':wpimath')
apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
dependencies {
implementation project(':wpimath')
}
sourceSets {
main {
resources {
srcDirs 'src/main/native/resources'
}
}
}
model {
components {}
binaries {
all {
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
return
}
it.cppCompiler.define 'WPILIB_EXPORTS'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
if ((it instanceof NativeExecutableBinarySpec || it instanceof GoogleTestTestSuiteBinarySpec) && it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
nativeUtils.useRequiredLibrary(it, 'ni_link_libraries', 'ni_runtime_libraries')
}
}
}
tasks {
def c = $.components
def found = false
def systemArch = getCurrentArch()
c.each {
if (it in NativeExecutableSpec && it.name == "${nativeName}Dev") {
it.binaries.each {
if (!found) {
def arch = it.targetPlatform.name
if (arch == systemArch) {
def filePath = it.tasks.install.installDirectory.get().toString() + File.separatorChar + 'lib'
found = true
}
}
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
// 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 edu.wpi.first.apriltag;
public final class DevMain {
/** Main entry point. */
public static void main(String[] args) {
System.out.println("Hello World!");
}
private DevMain() {}
}

View File

@@ -0,0 +1,5 @@
// 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.
int main() {}

View File

@@ -0,0 +1,47 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.geometry.Pose3d;
import java.util.Objects;
@SuppressWarnings("MemberName")
public class AprilTag {
@JsonProperty(value = "ID")
public int ID;
@JsonProperty(value = "pose")
public Pose3d pose;
@SuppressWarnings("ParameterName")
@JsonCreator
public AprilTag(
@JsonProperty(required = true, value = "ID") int ID,
@JsonProperty(required = true, value = "pose") Pose3d pose) {
this.ID = ID;
this.pose = pose;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AprilTag) {
var other = (AprilTag) obj;
return ID == other.ID && pose.equals(other.pose);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(ID, pose);
}
@Override
public String toString() {
return "AprilTag(ID: " + ID + ", pose: " + pose + ")";
}
}

View File

@@ -0,0 +1,236 @@
// 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 edu.wpi.first.apriltag;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation3d;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Class for representing a layout of AprilTags on a field and reading them from a JSON format.
*
* <p>The JSON format contains two top-level objects, "tags" and "field". The "tags" object is a
* list of all AprilTags contained within a layout. Each AprilTag serializes to a JSON object
* containing an ID and a Pose3d. The "field" object is a descriptor of the size of the field in
* meters with "width" and "length" values. This is to account for arbitrary field sizes when
* transforming the poses.
*
* <p>Pose3ds are assumed to be measured from the bottom-left corner of the field, when the blue
* alliance is at the left. By default, Pose3ds will be returned as declared when calling {@link
* AprilTagFieldLayout#getTagPose(int)}. {@link #setOrigin(OriginPosition)} can be used to transform
* the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be correct relative to a
* different coordinate frame.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
public class AprilTagFieldLayout {
public enum OriginPosition {
kBlueAllianceWallRightSide,
kRedAllianceWallRightSide,
}
private final Map<Integer, AprilTag> m_apriltags = new HashMap<>();
@JsonProperty(value = "field")
private FieldDimensions m_fieldDimensions;
private Pose3d m_origin;
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
* @throws IOException If reading from the file fails.
*/
public AprilTagFieldLayout(String path) throws IOException {
this(Path.of(path));
}
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
* @throws IOException If reading from the file fails.
*/
public AprilTagFieldLayout(Path path) throws IOException {
AprilTagFieldLayout layout =
new ObjectMapper().readValue(path.toFile(), AprilTagFieldLayout.class);
m_apriltags.putAll(layout.m_apriltags);
m_fieldDimensions = layout.m_fieldDimensions;
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
}
/**
* Construct a new AprilTagFieldLayout from a list of {@link AprilTag} objects.
*
* @param apriltags List of {@link AprilTag}.
* @param fieldLength Length of the field the layout is representing in meters.
* @param fieldWidth Width of the field the layout is representing in meters.
*/
public AprilTagFieldLayout(List<AprilTag> apriltags, double fieldLength, double fieldWidth) {
this(apriltags, new FieldDimensions(fieldLength, fieldWidth));
}
@JsonCreator
private AprilTagFieldLayout(
@JsonProperty(required = true, value = "tags") List<AprilTag> apriltags,
@JsonProperty(required = true, value = "field") FieldDimensions fieldDimensions) {
// To ensure the underlying semantics don't change with what kind of list is passed in
for (AprilTag tag : apriltags) {
m_apriltags.put(tag.ID, tag);
}
m_fieldDimensions = fieldDimensions;
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
}
/**
* Returns a List of the {@link AprilTag AprilTags} used in this layout.
*
* @return The {@link AprilTag AprilTags} used in this layout.
*/
@JsonProperty("tags")
public List<AprilTag> getTags() {
return new ArrayList<>(m_apriltags.values());
}
/**
* Sets the origin based on a predefined enumeration of coordinate frame origins. The origins are
* calculated from the field dimensions.
*
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
* relative to a predefined coordinate frame.
*
* @param origin The predefined origin
*/
@JsonIgnore
public void setOrigin(OriginPosition origin) {
switch (origin) {
case kBlueAllianceWallRightSide:
setOrigin(new Pose3d());
break;
case kRedAllianceWallRightSide:
setOrigin(
new Pose3d(
new Translation3d(m_fieldDimensions.fieldLength, m_fieldDimensions.fieldWidth, 0),
new Rotation3d(0, 0, Math.PI)));
break;
default:
throw new IllegalArgumentException("Unsupported enum value");
}
}
/**
* Sets the origin for tag pose transformation.
*
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
* relative to the provided origin.
*
* @param origin The new origin for tag transformations
*/
@JsonIgnore
public void setOrigin(Pose3d origin) {
m_origin = origin;
}
/**
* Gets an AprilTag pose by its ID.
*
* @param ID The ID of the tag.
* @return The pose corresponding to the ID passed in or an empty optional if a tag with that ID
* was not found.
*/
@SuppressWarnings("ParameterName")
public Optional<Pose3d> getTagPose(int ID) {
AprilTag tag = m_apriltags.get(ID);
if (tag == null) {
return Optional.empty();
}
return Optional.of(tag.pose.relativeTo(m_origin));
}
/**
* Serializes a AprilTagFieldLayout to a JSON file.
*
* @param path The path to write to.
* @throws IOException If writing to the file fails.
*/
public void serialize(String path) throws IOException {
serialize(Path.of(path));
}
/**
* Serializes a AprilTagFieldLayout to a JSON file.
*
* @param path The path to write to.
* @throws IOException If writing to the file fails.
*/
public void serialize(Path path) throws IOException {
new ObjectMapper().writeValue(path.toFile(), this);
}
/**
* Deserializes a field layout from a resource within a jar file.
*
* @param resourcePath The absolute path of the resource
* @return The deserialized layout
* @throws IOException If the resource could not be loaded
*/
public static AprilTagFieldLayout loadFromResource(String resourcePath) throws IOException {
try (InputStream stream = AprilTagFieldLayout.class.getResourceAsStream(resourcePath);
InputStreamReader reader = new InputStreamReader(stream)) {
return new ObjectMapper().readerFor(AprilTagFieldLayout.class).readValue(reader);
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AprilTagFieldLayout) {
var other = (AprilTagFieldLayout) obj;
return m_apriltags.equals(other.m_apriltags) && m_origin.equals(other.m_origin);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(m_apriltags, m_origin);
}
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
private static class FieldDimensions {
@SuppressWarnings("MemberName")
@JsonProperty(value = "length")
public double fieldLength;
@SuppressWarnings("MemberName")
@JsonProperty(value = "width")
public double fieldWidth;
@JsonCreator()
FieldDimensions(
@JsonProperty(required = true, value = "length") double fieldLength,
@JsonProperty(required = true, value = "width") double fieldWidth) {
this.fieldLength = fieldLength;
this.fieldWidth = fieldWidth;
}
}
}

View File

@@ -0,0 +1,20 @@
// 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 edu.wpi.first.apriltag;
public enum AprilTagFields {
k2022RapidReact("2022-rapidreact.json");
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
/** Alias to the current game. */
public static final AprilTagFields kDefaultField = k2022RapidReact;
public final String m_resourceFile;
AprilTagFields(String resourceFile) {
m_resourceFile = kBaseResourceDir + resourceFile;
}
}

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.
#include "frc/apriltag/AprilTag.h"
#include <wpi/json.h>
using namespace frc;
bool AprilTag::operator==(const AprilTag& other) const {
return ID == other.ID && pose == other.pose;
}
bool AprilTag::operator!=(const AprilTag& other) const {
return !operator==(other);
}
void frc::to_json(wpi::json& json, const AprilTag& apriltag) {
json = wpi::json{{"ID", apriltag.ID}, {"pose", apriltag.pose}};
}
void frc::from_json(const wpi::json& json, AprilTag& apriltag) {
apriltag.ID = json.at("ID").get<int>();
apriltag.pose = json.at("pose").get<Pose3d>();
}

View File

@@ -0,0 +1,117 @@
// 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.
#include "frc/apriltag/AprilTagFieldLayout.h"
#include <system_error>
#include <units/angle.h>
#include <units/length.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
using namespace frc;
AprilTagFieldLayout::AprilTagFieldLayout(std::string_view path) {
std::error_code error_code;
wpi::raw_fd_istream input{path, error_code};
if (error_code) {
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
}
wpi::json json;
input >> json;
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
m_apriltags[tag.ID] = tag;
}
m_fieldWidth = units::meter_t{json.at("field").at("width").get<double>()};
m_fieldLength = units::meter_t{json.at("field").at("height").get<double>()};
}
AprilTagFieldLayout::AprilTagFieldLayout(std::vector<AprilTag> apriltags,
units::meter_t fieldLength,
units::meter_t fieldWidth)
: m_fieldLength(std::move(fieldLength)),
m_fieldWidth(std::move(fieldWidth)) {
for (const auto& tag : apriltags) {
m_apriltags[tag.ID] = tag;
}
}
void AprilTagFieldLayout::SetOrigin(OriginPosition origin) {
switch (origin) {
case OriginPosition::kBlueAllianceWallRightSide:
SetOrigin(Pose3d{});
break;
case OriginPosition::kRedAllianceWallRightSide:
SetOrigin(Pose3d{Translation3d{m_fieldLength, m_fieldWidth, 0_m},
Rotation3d{0_deg, 0_deg, 180_deg}});
break;
default:
throw std::invalid_argument("Invalid origin");
}
}
void AprilTagFieldLayout::SetOrigin(const Pose3d& origin) {
m_origin = origin;
}
std::optional<frc::Pose3d> AprilTagFieldLayout::GetTagPose(int ID) const {
const auto& it = m_apriltags.find(ID);
if (it == m_apriltags.end()) {
return std::nullopt;
}
return it->second.pose.RelativeTo(m_origin);
}
void AprilTagFieldLayout::Serialize(std::string_view path) {
std::error_code error_code;
wpi::raw_fd_ostream output{path, error_code};
if (error_code) {
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
}
wpi::json json = *this;
output << json;
output.flush();
}
bool AprilTagFieldLayout::operator==(const AprilTagFieldLayout& other) const {
return m_apriltags == other.m_apriltags && m_origin == other.m_origin &&
m_fieldLength == other.m_fieldLength &&
m_fieldWidth == other.m_fieldWidth;
}
bool AprilTagFieldLayout::operator!=(const AprilTagFieldLayout& other) const {
return !operator==(other);
}
void frc::to_json(wpi::json& json, const AprilTagFieldLayout& layout) {
std::vector<AprilTag> tagVector;
tagVector.reserve(layout.m_apriltags.size());
for (const auto& pair : layout.m_apriltags) {
tagVector.push_back(pair.second);
}
json = wpi::json{{"field",
{{"length", layout.m_fieldLength.value()},
{"width", layout.m_fieldWidth.value()}}},
{"tags", tagVector}};
}
void frc::from_json(const wpi::json& json, AprilTagFieldLayout& layout) {
layout.m_apriltags.clear();
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
layout.m_apriltags[tag.ID] = tag;
}
layout.m_fieldLength =
units::meter_t{json.at("field").at("length").get<double>()};
layout.m_fieldWidth =
units::meter_t{json.at("field").at("width").get<double>()};
}

View File

@@ -0,0 +1,28 @@
// 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.
#include "frc/apriltag/AprilTagFields.h"
#include <wpi/json.h>
namespace frc {
// C++ generated from resource files
std::string_view GetResource_2022_rapidreact_json();
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
std::string_view fieldString;
switch (field) {
case AprilTagField::k2022RapidReact:
fieldString = GetResource_2022_rapidreact_json();
break;
case AprilTagField::kNumFields:
throw std::invalid_argument("Invalid Field");
}
wpi::json json = wpi::json::parse(fieldString);
return json.get<AprilTagFieldLayout>();
}
} // namespace frc

View File

@@ -0,0 +1,45 @@
// 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.
#pragma once
#include <wpi/SymbolExports.h>
#include "frc/geometry/Pose3d.h"
namespace wpi {
class json;
} // namespace wpi
namespace frc {
struct WPILIB_DLLEXPORT AprilTag {
int ID;
Pose3d pose;
/**
* Checks equality between this AprilTag and another object.
*
* @param other The other object.
* @return Whether the two objects are equal.
*/
bool operator==(const AprilTag& other) const;
/**
* Checks inequality between this AprilTag and another object.
*
* @param other The other object.
* @return Whether the two objects are not equal.
*/
bool operator!=(const AprilTag& other) const;
};
WPILIB_DLLEXPORT
void to_json(wpi::json& json, const AprilTag& apriltag);
WPILIB_DLLEXPORT
void from_json(const wpi::json& json, AprilTag& apriltag);
} // namespace frc

View File

@@ -0,0 +1,139 @@
// 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.
#pragma once
#include <optional>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <units/length.h>
#include <wpi/SymbolExports.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/geometry/Pose3d.h"
namespace wpi {
class json;
} // namespace wpi
namespace frc {
/**
* Class for representing a layout of AprilTags on a field and reading them from
* a JSON format.
*
* The JSON format contains two top-level objects, "tags" and "field".
* The "tags" object is a list of all AprilTags contained within a layout. Each
* AprilTag serializes to a JSON object containing an ID and a Pose3d. The
* "field" object is a descriptor of the size of the field in meters with
* "width" and "length" values. This is to account for arbitrary field sizes
* when transforming the poses.
*
* Pose3ds are assumed to be measured from the bottom-left corner of the field,
* when the blue alliance is at the left. By default, Pose3ds will be returned
* as declared when calling GetTagPose(int).
* SetOrigin(AprilTagFieldLayout::OriginPosition) can be used to transform the
* poses returned by GetTagPose(int) to be correct relative to a different
* coordinate frame.
*/
class WPILIB_DLLEXPORT AprilTagFieldLayout {
public:
enum class OriginPosition {
kBlueAllianceWallRightSide,
kRedAllianceWallRightSide,
};
AprilTagFieldLayout() = default;
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
*/
explicit AprilTagFieldLayout(std::string_view path);
/**
* Construct a new AprilTagFieldLayout from a vector of AprilTag objects.
*
* @param apriltags Vector of AprilTags.
* @param fieldLength Length of field the layout is representing.
* @param fieldWidth Width of field the layout is representing.
*/
AprilTagFieldLayout(std::vector<AprilTag> apriltags,
units::meter_t fieldLength, units::meter_t fieldWidth);
/**
* Sets the origin based on a predefined enumeration of coordinate frame
* origins. The origins are calculated from the field dimensions.
*
* This transforms the Pose3ds returned by GetTagPose(int) to return the
* correct pose relative to a predefined coordinate frame.
*
* @param origin The predefined origin
*/
void SetOrigin(OriginPosition origin);
/**
* Sets the origin for tag pose transformation.
*
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
* correct pose relative to the provided origin.
*
* @param origin The new origin for tag transformations
*/
void SetOrigin(const Pose3d& origin);
/**
* Gets an AprilTag pose by its ID.
*
* @param ID The ID of the tag.
* @return The pose corresponding to the ID that was passed in or an empty
* optional if a tag with that ID is not found.
*/
std::optional<Pose3d> GetTagPose(int ID) const;
/**
* Serializes an AprilTagFieldLayout to a JSON file.
*
* @param path The path to write the JSON file to.
*/
void Serialize(std::string_view path);
/*
* Checks equality between this AprilTagFieldLayout and another object.
*
* @param other The other object.
* @return Whether the two objects are equal.
*/
bool operator==(const AprilTagFieldLayout& other) const;
/**
* Checks inequality between this AprilTagFieldLayout and another object.
*
* @param other The other object.
* @return Whether the two objects are not equal.
*/
bool operator!=(const AprilTagFieldLayout& other) const;
private:
std::unordered_map<int, AprilTag> m_apriltags;
units::meter_t m_fieldLength;
units::meter_t m_fieldWidth;
Pose3d m_origin;
friend WPILIB_DLLEXPORT void to_json(wpi::json& json,
const AprilTagFieldLayout& layout);
friend WPILIB_DLLEXPORT void from_json(const wpi::json& json,
AprilTagFieldLayout& layout);
};
WPILIB_DLLEXPORT
void to_json(wpi::json& json, const AprilTagFieldLayout& layout);
WPILIB_DLLEXPORT
void from_json(const wpi::json& json, AprilTagFieldLayout& layout);
} // namespace frc

View File

@@ -0,0 +1,31 @@
// 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.
#pragma once
#include <string_view>
#include <wpi/SymbolExports.h>
#include "frc/apriltag/AprilTagFieldLayout.h"
namespace frc {
enum class AprilTagField {
k2022RapidReact,
// This is a placeholder for denoting the last supported field. This should
// always be the last entry in the enum and should not be used by users
kNumFields,
};
/**
* Loads an AprilTagFieldLayout from a predefined field
*
* @param field The predefined field
*/
WPILIB_DLLEXPORT AprilTagFieldLayout
LoadAprilTagLayoutField(AprilTagField field);
} // namespace frc

View File

@@ -0,0 +1,415 @@
{
"tags" : [ {
"ID" : 0,
"pose" : {
"translation" : {
"x" : -0.0035306,
"y" : 7.578928199999999,
"z" : 0.8858503999999999
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 1,
"pose" : {
"translation" : {
"x" : 3.2327088,
"y" : 5.486654,
"z" : 1.7254728
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 2,
"pose" : {
"translation" : {
"x" : 3.067812,
"y" : 5.3305202,
"z" : 1.3762228
},
"rotation" : {
"quaternion" : {
"W" : 0.7071067811865476,
"X" : 0.0,
"Y" : 0.0,
"Z" : -0.7071067811865475
}
}
}
}, {
"ID" : 3,
"pose" : {
"translation" : {
"x" : 0.0039878,
"y" : 5.058536999999999,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 4,
"pose" : {
"translation" : {
"x" : 0.0039878,
"y" : 3.5124898,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 5,
"pose" : {
"translation" : {
"x" : 0.12110719999999998,
"y" : 1.7178274,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 6,
"pose" : {
"translation" : {
"x" : 0.8733027999999999,
"y" : 0.9412985999999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 7,
"pose" : {
"translation" : {
"x" : 1.6150844,
"y" : 0.15725139999999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 10,
"pose" : {
"translation" : {
"x" : 16.4627306,
"y" : 0.6506718,
"z" : 0.8858503999999999
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 11,
"pose" : {
"translation" : {
"x" : 13.2350002,
"y" : 2.743454,
"z" : 1.7254728
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 12,
"pose" : {
"translation" : {
"x" : 13.391388000000001,
"y" : 2.8998418,
"z" : 1.3762228
},
"rotation" : {
"quaternion" : {
"W" : 0.7071067811865476,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.7071067811865475
}
}
}
}, {
"ID" : 13,
"pose" : {
"translation" : {
"x" : 16.4552122,
"y" : 3.1755079999999998,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 14,
"pose" : {
"translation" : {
"x" : 16.4552122,
"y" : 4.7171356,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 15,
"pose" : {
"translation" : {
"x" : 16.3350194,
"y" : 6.5149729999999995,
"z" : 0.8937752
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 16,
"pose" : {
"translation" : {
"x" : 15.5904946,
"y" : 7.292695599999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 17,
"pose" : {
"translation" : {
"x" : 14.847188999999998,
"y" : 8.0691228,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 40,
"pose" : {
"translation" : {
"x" : 7.874127,
"y" : 4.9131728,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.5446390350150271,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.838670567945424
}
}
}
}, {
"ID" : 41,
"pose" : {
"translation" : {
"x" : 7.4312271999999995,
"y" : 3.759327,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : -0.20791169081775934,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9781476007338057
}
}
}
}, {
"ID" : 42,
"pose" : {
"translation" : {
"x" : 8.585073,
"y" : 3.3164272,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.838670567945424,
"X" : 0.0,
"Y" : 0.0,
"Z" : -0.5446390350150271
}
}
}
}, {
"ID" : 43,
"pose" : {
"translation" : {
"x" : 9.0279728,
"y" : 4.470273,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.9781476007338057,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.20791169081775934
}
}
}
}, {
"ID" : 50,
"pose" : {
"translation" : {
"x" : 7.6790296,
"y" : 4.3261534,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : 0.17729273396782605,
"X" : -0.22744989571511945,
"Y" : 0.04215534644161733,
"Z" : 0.9565859910053995
}
}
}
}, {
"ID" : 51,
"pose" : {
"translation" : {
"x" : 8.0182466,
"y" : 3.5642296,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : -0.5510435465842192,
"X" : -0.19063969497246985,
"Y" : -0.13102303230819815,
"Z" : 0.8017733354717242
}
}
}
}, {
"ID" : 52,
"pose" : {
"translation" : {
"x" : 8.7801704,
"y" : 3.9034466,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : -0.9565859910053994,
"X" : -0.04215534644161739,
"Y" : -0.22744989571511942,
"Z" : 0.17729273396782633
}
}
}
}, {
"ID" : 53,
"pose" : {
"translation" : {
"x" : 8.4409534,
"y" : 4.6653704,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : 0.8017733354717241,
"X" : -0.1310230323081982,
"Y" : 0.19063969497246983,
"Z" : 0.5510435465842194
}
}
}
} ],
"field" : {
"length" : 8.2296,
"width" : 16.4592
}
}

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 edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertEquals;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.util.Units;
import java.util.List;
import org.junit.jupiter.api.Test;
class AprilTagPoseSetOriginTest {
@Test
void transformationMatches() {
var layout =
new AprilTagFieldLayout(
List.of(
new AprilTag(1, new Pose3d(new Translation3d(0, 0, 0), new Rotation3d(0, 0, 0))),
new AprilTag(
2,
new Pose3d(
new Translation3d(
Units.feetToMeters(4.0), Units.feetToMeters(4), Units.feetToMeters(4)),
new Rotation3d(0, 0, Units.degreesToRadians(180))))),
Units.feetToMeters(54.0),
Units.feetToMeters(27.0));
layout.setOrigin(AprilTagFieldLayout.OriginPosition.kRedAllianceWallRightSide);
assertEquals(
new Pose3d(
new Translation3d(Units.feetToMeters(54.0), Units.feetToMeters(27.0), 0.0),
new Rotation3d(0.0, 0.0, Units.degreesToRadians(180.0))),
layout.getTagPose(1).orElse(null));
assertEquals(
new Pose3d(
new Translation3d(
Units.feetToMeters(50.0), Units.feetToMeters(23.0), Units.feetToMeters(4)),
new Rotation3d(0.0, 0.0, 0)),
layout.getTagPose(2).orElse(null));
}
}

View File

@@ -0,0 +1,38 @@
// 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 edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.util.Units;
import java.util.List;
import org.junit.jupiter.api.Test;
class AprilTagSerializationTest {
@Test
void deserializeMatches() {
var layout =
new AprilTagFieldLayout(
List.of(
new AprilTag(1, new Pose3d(0, 0, 0, new Rotation3d(0, 0, 0))),
new AprilTag(3, new Pose3d(0, 1, 0, new Rotation3d(0, 0, 0)))),
Units.feetToMeters(54.0),
Units.feetToMeters(27.0));
var objectMapper = new ObjectMapper();
var deserialized =
assertDoesNotThrow(
() ->
objectMapper.readValue(
objectMapper.writeValueAsString(layout), AprilTagFieldLayout.class));
assertEquals(layout, deserialized);
}
}

View File

@@ -0,0 +1,74 @@
// 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 edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.util.Units;
import java.io.IOException;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class LoadConfigTest {
@ParameterizedTest
@EnumSource(AprilTagFields.class)
void testLoad(AprilTagFields field) {
AprilTagFieldLayout layout =
Assertions.assertDoesNotThrow(
() -> AprilTagFieldLayout.loadFromResource(field.m_resourceFile));
assertNotNull(layout);
}
@Test
void test2022RapidReact() throws IOException {
AprilTagFieldLayout layout =
AprilTagFieldLayout.loadFromResource(AprilTagFields.k2022RapidReact.m_resourceFile);
// Blue Hangar Truss - Hub
Pose3d expectedPose =
new Pose3d(
Units.inchesToMeters(127.272),
Units.inchesToMeters(216.01),
Units.inchesToMeters(67.932),
new Rotation3d(0, 0, 0));
Optional<Pose3d> maybePose = layout.getTagPose(1);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Blue Terminal Near Station
expectedPose =
new Pose3d(
Units.inchesToMeters(4.768),
Units.inchesToMeters(67.631),
Units.inchesToMeters(35.063),
new Rotation3d(0, 0, Math.toRadians(46.25)));
maybePose = layout.getTagPose(5);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Upper Hub Blue-Near
expectedPose =
new Pose3d(
Units.inchesToMeters(332.321),
Units.inchesToMeters(183.676),
Units.inchesToMeters(95.186),
new Rotation3d(0, Math.toRadians(26.75), Math.toRadians(69)));
maybePose = layout.getTagPose(53);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Doesn't exist
maybePose = layout.getTagPose(54);
assertFalse(maybePose.isPresent());
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <vector>
#include <wpi/json.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/apriltag/AprilTagFieldLayout.h"
#include "frc/geometry/Pose3d.h"
#include "gtest/gtest.h"
using namespace frc;
TEST(AprilTagJsonTest, DeserializeMatches) {
auto layout = AprilTagFieldLayout{
std::vector{
AprilTag{1, Pose3d{}},
AprilTag{3, Pose3d{0_m, 1_m, 0_m, Rotation3d{0_deg, 0_deg, 0_deg}}}},
54_ft, 27_ft};
AprilTagFieldLayout deserialized;
wpi::json json = layout;
EXPECT_NO_THROW(deserialized = json.get<AprilTagFieldLayout>());
EXPECT_EQ(layout, deserialized);
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <vector>
#include <wpi/json.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/apriltag/AprilTagFieldLayout.h"
#include "frc/geometry/Pose3d.h"
#include "gtest/gtest.h"
using namespace frc;
TEST(AprilTagPoseSetOriginTest, TransformationMatches) {
auto layout = AprilTagFieldLayout{
std::vector<AprilTag>{
AprilTag{1,
Pose3d{0_ft, 0_ft, 0_ft, Rotation3d{0_deg, 0_deg, 0_deg}}},
AprilTag{
2, Pose3d{4_ft, 4_ft, 4_ft, Rotation3d{0_deg, 0_deg, 180_deg}}}},
54_ft, 27_ft};
layout.SetOrigin(
AprilTagFieldLayout::OriginPosition::kRedAllianceWallRightSide);
auto mirrorPose =
Pose3d{54_ft, 27_ft, 0_ft, Rotation3d{0_deg, 0_deg, 180_deg}};
EXPECT_EQ(mirrorPose, *layout.GetTagPose(1));
mirrorPose = Pose3d{50_ft, 23_ft, 4_ft, Rotation3d{0_deg, 0_deg, 0_deg}};
EXPECT_EQ(mirrorPose, *layout.GetTagPose(2));
}

View File

@@ -0,0 +1,61 @@
// 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.
#include "frc/apriltag/AprilTagFields.h"
#include "gtest/gtest.h"
namespace frc {
std::vector<AprilTagField> GetAllFields() {
std::vector<AprilTagField> output;
for (int i = 0; i < static_cast<int>(AprilTagField::kNumFields); ++i) {
output.push_back(static_cast<AprilTagField>(i));
}
return output;
}
TEST(AprilTagFieldsTest, TestLoad2022RapidReact) {
AprilTagFieldLayout layout =
LoadAprilTagLayoutField(AprilTagField::k2022RapidReact);
// Blue Hangar Truss - Hub
auto expectedPose =
Pose3d{127.272_in, 216.01_in, 67.932_in, Rotation3d{0_deg, 0_deg, 0_deg}};
auto maybePose = layout.GetTagPose(1);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Blue Terminal Near Station
expectedPose = Pose3d{4.768_in, 67.631_in, 35.063_in,
Rotation3d{0_deg, 0_deg, 46.25_deg}};
maybePose = layout.GetTagPose(5);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Upper Hub Blue-Near
expectedPose = Pose3d{332.321_in, 183.676_in, 95.186_in,
Rotation3d{0_deg, 26.75_deg, 69_deg}};
maybePose = layout.GetTagPose(53);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Doesn't exist
maybePose = layout.GetTagPose(54);
EXPECT_FALSE(maybePose);
}
// Test all of the fields in the enum
class AllFieldsFixtureTest : public ::testing::TestWithParam<AprilTagField> {};
TEST_P(AllFieldsFixtureTest, CheckEntireEnum) {
AprilTagField field = GetParam();
EXPECT_NO_THROW(LoadAprilTagLayoutField(field));
}
INSTANTIATE_TEST_SUITE_P(ValuesEnumTestInstTests, AllFieldsFixtureTest,
::testing::ValuesIn(GetAllFields()));
} // namespace frc

View File

@@ -0,0 +1,11 @@
// 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.
#include "gtest/gtest.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}

View File

@@ -13,10 +13,10 @@ buildscript {
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.1.0'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.0'
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
id 'edu.wpi.first.NativeUtils' apply false
id 'edu.wpi.first.GradleJni' version '1.0.0'
id 'edu.wpi.first.GradleJni' version '1.1.0'
id 'edu.wpi.first.GradleVsCode'
id 'idea'
id 'visual-studio'
@@ -136,8 +136,10 @@ subprojects {
}
// Sign outputs with Developer ID
if (project.hasProperty("developerID")) {
tasks.withType(AbstractLinkTask) { task ->
tasks.withType(AbstractLinkTask) { task ->
task.inputs.property "HasDeveloperId", project.hasProperty("developerID")
if (project.hasProperty("developerID")) {
// Don't sign any executables because codesign complains
// about relative rpath.
if (!(task instanceof LinkExecutable)) {

View File

@@ -5,5 +5,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2023.4.0"
implementation "edu.wpi.first:native-utils:2023.8.0"
}

View File

@@ -140,6 +140,12 @@ int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
}
return 100;
}
// Arducam OV9281 exposure setting quirk
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 1 && rawProp.maximum == 5000) {
// real range is 1-75
return 100.0 * (rawValue - 1) / (75 - 1);
}
return 100.0 * (rawValue - rawProp.minimum) /
(rawProp.maximum - rawProp.minimum);
}
@@ -159,6 +165,12 @@ int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp,
}
return quirkLifeCamHd3000[ndx];
}
// Arducam OV9281 exposure setting quirk
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 1 && rawProp.maximum == 5000) {
// real range is 1-75
return 1 + (75 - 1) * (percentValue / 100.0);
}
return rawProp.minimum +
(rawProp.maximum - rawProp.minimum) * (percentValue / 100.0);
}
@@ -1384,6 +1396,7 @@ void UsbCameraImpl::SetQuirks() {
std::string_view desc = GetDescription(descbuf);
m_lifecam_exposure = wpi::ends_with(desc, "LifeCam HD-3000") ||
wpi::ends_with(desc, "LifeCam Cinema (TM)");
m_ov9281_exposure = wpi::contains(desc, "OV9281");
m_picamera = wpi::ends_with(desc, "mmal service");
int deviceNum = GetDeviceNum(m_path.c_str());

View File

@@ -161,6 +161,7 @@ class UsbCameraImpl : public SourceImpl {
// Quirks
bool m_lifecam_exposure{false}; // Microsoft LifeCam exposure
bool m_ps3eyecam_exposure{false}; // PS3 Eyecam exposure
bool m_ov9281_exposure{false}; // Arducam OV9281 exposure
bool m_picamera{false}; // Raspberry Pi camera
//

View File

@@ -37,6 +37,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

View File

@@ -126,6 +126,9 @@ doxygen {
exclude 'units/**'
}
//TODO: building memory docs causes search to break
exclude 'wpi/memory/**'
aliases 'effects=\\par <i>Effects:</i>^^',
'notes=\\par <i>Notes:</i>^^',
'requires=\\par <i>Requires:</i>^^',

View File

@@ -92,6 +92,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

View File

@@ -53,11 +53,12 @@ struct PlotSeriesRef {
};
class PlotSeries {
explicit PlotSeries(Storage& storage, int yAxis = 0);
explicit PlotSeries(Storage& storage);
public:
PlotSeries(Storage& storage, std::string_view id);
PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
PlotSeries(Storage& storage, DataSource* source);
PlotSeries(Storage& storage, DataSource* source, int yAxis);
const std::string& GetId() const { return m_id; }
@@ -195,7 +196,7 @@ class PlotView : public View {
} // namespace
PlotSeries::PlotSeries(Storage& storage, int yAxis)
PlotSeries::PlotSeries(Storage& storage)
: m_id{storage.GetString("id")},
m_name{storage.GetString("name")},
m_yAxis{storage.GetInt("yAxis", 0)},
@@ -208,12 +209,10 @@ PlotSeries::PlotSeries(Storage& storage, int yAxis)
m_digital{
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {
m_yAxis = yAxis;
}
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
: PlotSeries{storage, 0} {
: PlotSeries{storage} {
m_id = id;
if (DataSource* source = DataSource::Find(id)) {
SetSource(source);
@@ -222,12 +221,17 @@ PlotSeries::PlotSeries(Storage& storage, std::string_view id)
CheckSource();
}
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
: PlotSeries{storage, yAxis} {
PlotSeries::PlotSeries(Storage& storage, DataSource* source)
: PlotSeries{storage} {
SetSource(source);
m_id = source->GetId();
}
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
: PlotSeries{storage, source} {
m_yAxis = yAxis;
}
void PlotSeries::CheckSource() {
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
m_source = nullptr;

View File

@@ -289,16 +289,13 @@ void NTMechanism2DModel::Update() {
}
}
} else if (auto valueData = event.GetValueEventData()) {
// .name
if (valueData->topic == m_nameTopic.GetHandle()) {
// .name
if (valueData->value && valueData->value.IsString()) {
m_nameValue = valueData->value.GetString();
}
continue;
}
// dims
if (valueData->topic == m_dimensionsTopic.GetHandle()) {
} else if (valueData->topic == m_dimensionsTopic.GetHandle()) {
// dims
if (valueData->value && valueData->value.IsDoubleArray()) {
auto arr = valueData->value.GetDoubleArray();
if (arr.size() == 2) {
@@ -306,13 +303,32 @@ void NTMechanism2DModel::Update() {
units::meter_t{arr[1]}};
}
}
}
// backgroundColor
if (valueData->topic == m_bgColorTopic.GetHandle()) {
} else if (valueData->topic == m_bgColorTopic.GetHandle()) {
// backgroundColor
if (valueData->value && valueData->value.IsString()) {
ConvertColor(valueData->value.GetString(), &m_bgColorValue);
}
} else {
auto fullName = nt::Topic{valueData->topic}.GetName();
auto name = wpi::drop_front(fullName, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
std::string_view childName;
std::tie(name, childName) = wpi::split(name, '/');
if (childName.empty()) {
continue;
}
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
[](const auto& e, std::string_view name) {
return e->GetName() < name;
});
if (it != m_roots.end() && (*it)->GetName() == name) {
if ((*it)->NTUpdate(event, childName)) {
m_roots.erase(it);
}
}
}
}
}

View File

@@ -19,16 +19,19 @@
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include "HALInitializer.h"
#include "hal/DriverStation.h"
#include "hal/Errors.h"
static_assert(sizeof(int32_t) >= sizeof(int),
"FRC_NetworkComm status variable is larger than 32 bits");
namespace {
struct HAL_JoystickAxesInt {
int16_t count;
int16_t axes[HAL_kMaxJoystickAxes];
};
} // namespace
namespace {
struct JoystickDataCache {
@@ -57,10 +60,11 @@ static wpi::mutex msgMutex;
static int32_t HAL_GetJoystickAxesInternal(int32_t joystickNum,
HAL_JoystickAxes* axes) {
JoystickAxes_t netcommAxes;
HAL_JoystickAxesInt netcommAxes;
int retVal = FRC_NetworkCommunication_getJoystickAxes(
joystickNum, &netcommAxes, HAL_kMaxJoystickAxes);
joystickNum, reinterpret_cast<JoystickAxes_t*>(&netcommAxes),
HAL_kMaxJoystickAxes);
// copy integer values to double values
axes->count = netcommAxes.count;
@@ -443,6 +447,7 @@ void HAL_RefreshDSData(void) {
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
hal::init::CheckInit();
driverStation->newDataEvents.Add(handle);
}

View File

@@ -82,6 +82,7 @@ void InitializeHAL() {
} // namespace init
void ReleaseFPGAInterrupt(int32_t interruptNumber) {
hal::init::CheckInit();
if (!global) {
return;
}
@@ -252,6 +253,7 @@ HAL_RuntimeType HAL_GetRuntimeType(void) {
}
int32_t HAL_GetFPGAVersion(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -260,6 +262,7 @@ int32_t HAL_GetFPGAVersion(int32_t* status) {
}
int64_t HAL_GetFPGARevision(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -268,6 +271,7 @@ int64_t HAL_GetFPGARevision(int32_t* status) {
}
uint64_t HAL_GetFPGATime(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -314,6 +318,7 @@ uint64_t HAL_ExpandFPGATime(uint32_t unexpandedLower, int32_t* status) {
}
HAL_Bool HAL_GetFPGAButton(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;
@@ -322,6 +327,7 @@ HAL_Bool HAL_GetFPGAButton(int32_t* status) {
}
HAL_Bool HAL_GetSystemActive(int32_t* status) {
hal::init::CheckInit();
if (!watchdog) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;
@@ -330,6 +336,7 @@ HAL_Bool HAL_GetSystemActive(int32_t* status) {
}
HAL_Bool HAL_GetBrownedOut(int32_t* status) {
hal::init::CheckInit();
if (!watchdog) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;

View File

@@ -326,6 +326,7 @@ void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
if (gShutdown) {
return;
}
hal::init::CheckInit();
driverStation->newDataEvents.Add(handle);
}

View File

@@ -53,6 +53,10 @@ dependencies {
implementation project(':wpilibNewCommands')
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {
onlyIf { false }
}
def simProjects = ['halsim_gui']
deploy {

View File

@@ -7,6 +7,7 @@ package edu.wpi.first.networktables;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.concurrent.Event;
import edu.wpi.first.util.datalog.DataLog;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -35,16 +36,33 @@ import java.util.function.Consumer;
*/
@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class NetworkTableInstance implements AutoCloseable {
/**
* Client/server mode flag values (as returned by {@link #getNetworkMode()}). This is a bitmask.
*/
public static final int kNetModeNone = 0x00;
/** Client/server mode flag values (as returned by {@link #getNetworkMode()}). */
public enum NetworkMode {
/** Running in server mode. */
kServer(0x01),
public static final int kNetModeServer = 0x01;
public static final int kNetModeClient3 = 0x02;
public static final int kNetModeClient4 = 0x04;
public static final int kNetModeStarting = 0x08;
public static final int kNetModeLocal = 0x10;
/** Running in NT3 client mode. */
kClient3(0x02),
/** Running in NT4 client mode. */
kClient4(0x04),
/** Currently starting up (either client or server). */
kStarting(0x08),
/** Running in local-only mode. */
kLocal(0x10);
private final int value;
NetworkMode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/** The default port that network tables operates on for NT3. */
public static final int kDefaultPort3 = 1735;
@@ -68,6 +86,7 @@ public final class NetworkTableInstance implements AutoCloseable {
if (m_owned && m_handle != 0) {
m_listeners.close();
NetworkTablesJNI.destroyInstance(m_handle);
m_handle = 0;
}
}
@@ -369,14 +388,17 @@ public final class NetworkTableInstance implements AutoCloseable {
m_inst = inst;
}
int add(String[] prefixes, int mask, Consumer<NetworkTableEvent> listener) {
int add(
String[] prefixes,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
m_lock.lock();
try {
if (m_poller == 0) {
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
startThread();
}
int h = NetworkTablesJNI.addListener(m_poller, prefixes, mask);
int h = NetworkTablesJNI.addListener(m_poller, prefixes, eventKinds);
m_listeners.put(h, listener);
return h;
} finally {
@@ -384,14 +406,17 @@ public final class NetworkTableInstance implements AutoCloseable {
}
}
int add(int handle, int mask, Consumer<NetworkTableEvent> listener) {
int add(
int handle,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
m_lock.lock();
try {
if (m_poller == 0) {
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
startThread();
}
int h = NetworkTablesJNI.addListener(m_poller, handle, mask);
int h = NetworkTablesJNI.addListener(m_poller, handle, eventKinds);
m_listeners.put(h, listener);
return h;
} finally {
@@ -562,9 +587,11 @@ public final class NetworkTableInstance implements AutoCloseable {
*/
public int addConnectionListener(
boolean immediateNotify, Consumer<NetworkTableEvent> listener) {
return m_listeners.add(m_handle,
NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0),
listener);
EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection);
if (immediateNotify) {
eventKinds.add(NetworkTableEvent.Kind.kImmediate);
}
return m_listeners.add(m_handle, eventKinds, listener);
}
/**
@@ -576,16 +603,18 @@ public final class NetworkTableInstance implements AutoCloseable {
* listener.
*
* @param topic Topic
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
Topic topic, int eventMask, Consumer<NetworkTableEvent> listener) {
Topic topic,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (topic.getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("topic is not from this instance");
}
return m_listeners.add(topic.getHandle(), eventMask, listener);
return m_listeners.add(topic.getHandle(), eventKinds, listener);
}
/**
@@ -595,16 +624,18 @@ public final class NetworkTableInstance implements AutoCloseable {
* active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
Subscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
Subscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (subscriber.getTopic().getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("subscriber is not from this instance");
}
return m_listeners.add(subscriber.getHandle(), eventMask, listener);
return m_listeners.add(subscriber.getHandle(), eventKinds, listener);
}
/**
@@ -614,16 +645,18 @@ public final class NetworkTableInstance implements AutoCloseable {
* active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
MultiSubscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
MultiSubscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (subscriber.getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("subscriber is not from this instance");
}
return m_listeners.add(subscriber.getHandle(), eventMask, listener);
return m_listeners.add(subscriber.getHandle(), eventKinds, listener);
}
/**
@@ -632,16 +665,18 @@ public final class NetworkTableInstance implements AutoCloseable {
* accessing any shared state from the callback function.
*
* @param entry Entry
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
NetworkTableEntry entry, int eventMask, Consumer<NetworkTableEvent> listener) {
NetworkTableEntry entry,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (entry.getTopic().getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("entry is not from this instance");
}
return m_listeners.add(entry.getHandle(), eventMask, listener);
return m_listeners.add(entry.getHandle(), eventKinds, listener);
}
/**
@@ -654,15 +689,15 @@ public final class NetworkTableInstance implements AutoCloseable {
* listener.
*
* @param prefixes Topic name string prefixes
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
String[] prefixes,
int eventMask,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
return m_listeners.add(prefixes, eventMask, listener);
return m_listeners.add(prefixes, eventKinds, listener);
}
/*
@@ -672,10 +707,17 @@ public final class NetworkTableInstance implements AutoCloseable {
/**
* Get the current network mode.
*
* @return Bitmask of NetworkMode.
* @return Enum set of NetworkMode.
*/
public int getNetworkMode() {
return NetworkTablesJNI.getNetworkMode(m_handle);
public EnumSet<NetworkMode> getNetworkMode() {
int flags = NetworkTablesJNI.getNetworkMode(m_handle);
EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class);
for (NetworkMode mode : NetworkMode.values()) {
if ((flags & mode.getValue()) != 0) {
rv.add(mode);
}
}
return rv;
}
/**
@@ -986,5 +1028,5 @@ public final class NetworkTableInstance implements AutoCloseable {
}
private boolean m_owned;
private final int m_handle;
private int m_handle;
}

View File

@@ -7,6 +7,7 @@ package edu.wpi.first.networktables;
import edu.wpi.first.util.RuntimeLoader;
import edu.wpi.first.util.datalog.DataLog;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicBoolean;
public final class NetworkTablesJNI {
@@ -203,6 +204,22 @@ public final class NetworkTablesJNI {
public static native void destroyListenerPoller(int poller);
private static int kindsToMask(EnumSet<NetworkTableEvent.Kind> kinds) {
int mask = 0;
for (NetworkTableEvent.Kind kind : kinds) {
mask |= kind.getValue();
}
return mask;
}
public static int addListener(int poller, String[] prefixes, EnumSet<NetworkTableEvent.Kind> kinds) {
return addListener(poller, prefixes, kindsToMask(kinds));
}
public static int addListener(int poller, int handle, EnumSet<NetworkTableEvent.Kind> kinds) {
return addListener(poller, handle, kindsToMask(kinds));
}
public static native int addListener(int poller, String[] prefixes, int mask);
public static native int addListener(int poller, int handle, int mask);

View File

@@ -7,52 +7,61 @@ package edu.wpi.first.networktables;
/**
* NetworkTables event.
*
* <p>Events have flags. The flags are a bitmask and must be OR'ed together when listening to an
* event to indicate the combination of events desired to be received.
* <p>There are different kinds of events. When creating a listener, a combination of event kinds
* can be listened to by building an EnumSet of NetworkTableEvent.Kind.
*/
@SuppressWarnings("MemberName")
public final class NetworkTableEvent {
/** No flags. */
public static final int kNone = 0;
public enum Kind {
/**
* Initial listener addition. Set this to receive immediate notification of matches to other
* criteria.
*/
kImmediate(0x0001),
/**
* Initial listener addition. Set this flag to receive immediate notification of matches to the
* flag criteria.
*/
public static final int kImmediate = 0x01;
/** Client connected (on server, any client connected). */
kConnected(0x0002),
/** Client connected (on server, any client connected). */
public static final int kConnected = 0x02;
/** Client disconnected (on server, any client disconnected). */
kDisconnected(0x0004),
/** Client disconnected (on server, any client disconnected). */
public static final int kDisconnected = 0x04;
/** Any connection event (connect or disconnect). */
kConnection(0x0004 | 0x0002),
/** Any connection event (connect or disconnect). */
public static final int kConnection = kConnected | kDisconnected;
/** New topic published. */
kPublish(0x0008),
/** New topic published. */
public static final int kPublish = 0x08;
/** Topic unpublished. */
kUnpublish(0x0010),
/** Topic unpublished. */
public static final int kUnpublish = 0x10;
/** Topic properties changed. */
kProperties(0x0020),
/** Topic properties changed. */
public static final int kProperties = 0x20;
/** Any topic event (publish, unpublish, or properties changed). */
kTopic(0x0020 | 0x0010 | 0x0008),
/** Any topic event (publish, unpublish, or properties changed). */
public static final int kTopic = kPublish | kUnpublish | kProperties;
/** Topic value updated (via network). */
kValueRemote(0x0040),
/** Topic value updated (via network). */
public static final int kValueRemote = 0x40;
/** Topic value updated (local). */
kValueLocal(0x0080),
/** Topic value updated (local). */
public static final int kValueLocal = 0x80;
/** Topic value updated (network or local). */
kValueAll(0x0080 | 0x0040),
/** Topic value updated (network or local). */
public static final int kValueAll = kValueRemote | kValueLocal;
/** Log message. */
kLogMessage(0x0100);
/** Log message. */
public static final int kLogMessage = 0x100;
private final int value;
Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* Handle of listener that was triggered. The value returned when adding the listener can be used
@@ -61,8 +70,8 @@ public final class NetworkTableEvent {
public final int listener;
/**
* Event flags. For example, kPublish if the topic was not previously published. Also indicates
* the data included with the event:
* Determine if event is of a particular kind. For example, kPublish if the topic was not
* previously published. Also indicates the data included with the event:
*
* <ul>
* <li>kConnected or kDisconnected: connInfo
@@ -70,8 +79,15 @@ public final class NetworkTableEvent {
* <li>kValueRemote, kValueLocal: valueData
* <li>kLogMessage: logMessage
* </ul>
*
* @param kind Kind
* @return True if event matches kind
*/
public final int flags;
public boolean is(Kind kind) {
return (m_flags & kind.getValue()) != 0;
}
private final int m_flags;
/** Connection information (for connection events). */
public final ConnectionInfo connInfo;
@@ -106,7 +122,7 @@ public final class NetworkTableEvent {
LogMessage logMessage) {
this.m_inst = inst;
this.listener = listener;
this.flags = flags;
this.m_flags = flags;
this.connInfo = connInfo;
this.topicInfo = topicInfo;
this.valueData = valueData;

View File

@@ -4,6 +4,7 @@
package edu.wpi.first.networktables;
import java.util.EnumSet;
import java.util.function.Consumer;
/**
@@ -18,16 +19,16 @@ public final class NetworkTableListener implements AutoCloseable {
*
* @param inst Instance
* @param prefixes Topic name string prefixes
* @param eventMask Bitmask of NetworkTableEvent flags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
NetworkTableInstance inst,
String[] prefixes,
int eventMask,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
return new NetworkTableListener(inst, inst.addListener(prefixes, eventMask, listener));
return new NetworkTableListener(inst, inst.addListener(prefixes, eventKinds, listener));
}
/**
@@ -35,56 +36,64 @@ public final class NetworkTableListener implements AutoCloseable {
* subscriber with the lifetime of the listener.
*
* @param topic Topic
* @param eventMask Bitmask of NetworkTableEvent flags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
Topic topic, int eventMask, Consumer<NetworkTableEvent> listener) {
Topic topic,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = topic.getInstance();
return new NetworkTableListener(inst, inst.addListener(topic, eventMask, listener));
return new NetworkTableListener(inst, inst.addListener(topic, eventKinds, listener));
}
/**
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of NetworkTableEvent flags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
Subscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
Subscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = subscriber.getTopic().getInstance();
return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener));
return new NetworkTableListener(inst, inst.addListener(subscriber, eventKinds, listener));
}
/**
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of NetworkTableEvent flags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
MultiSubscriber subscriber, int eventMask, Consumer<NetworkTableEvent> listener) {
MultiSubscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = subscriber.getInstance();
return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener));
return new NetworkTableListener(inst, inst.addListener(subscriber, eventKinds, listener));
}
/**
* Create a listener for topic changes on an entry.
*
* @param entry Entry
* @param eventMask Bitmask of NetworkTableEvent flags values
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
NetworkTableEntry entry, int eventMask, Consumer<NetworkTableEvent> listener) {
NetworkTableEntry entry,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = entry.getInstance();
return new NetworkTableListener(inst, inst.addListener(entry, eventMask, listener));
return new NetworkTableListener(inst, inst.addListener(entry, eventKinds, listener));
}
/**

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.networktables;
import java.util.EnumSet;
/**
* Topic change listener. This queues topic change events matching the specified mask. Code using
* the listener must periodically call readQueue() to read the events.
@@ -24,11 +26,11 @@ public final class NetworkTableListenerPoller implements AutoCloseable {
* prefixes. This creates a corresponding internal subscriber with the lifetime of the listener.
*
* @param prefixes Topic name string prefixes
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(String[] prefixes, int eventMask) {
return NetworkTablesJNI.addListener(m_handle, prefixes, eventMask);
public int addListener(String[] prefixes, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, prefixes, eventKinds);
}
/**
@@ -36,44 +38,44 @@ public final class NetworkTableListenerPoller implements AutoCloseable {
* subscriber with the lifetime of the listener.
*
* @param topic Topic
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(Topic topic, int eventMask) {
return NetworkTablesJNI.addListener(m_handle, topic.getHandle(), eventMask);
public int addListener(Topic topic, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, topic.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(Subscriber subscriber, int eventMask) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask);
public int addListener(Subscriber subscriber, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(MultiSubscriber subscriber, int eventMask) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask);
public int addListener(MultiSubscriber subscriber, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on an entry.
*
* @param entry Entry
* @param eventMask Bitmask of TopicListenerFlags values
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(NetworkTableEntry entry, int eventMask) {
return NetworkTablesJNI.addListener(m_handle, entry.getHandle(), eventMask);
public int addListener(NetworkTableEntry entry, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, entry.getHandle(), eventKinds);
}
/**
@@ -85,10 +87,11 @@ public final class NetworkTableListenerPoller implements AutoCloseable {
* @return Listener handle
*/
public int addConnectionListener(boolean immediateNotify) {
return NetworkTablesJNI.addListener(
m_handle,
m_inst.getHandle(),
NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0));
EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection);
if (immediateNotify) {
eventKinds.add(NetworkTableEvent.Kind.kImmediate);
}
return NetworkTablesJNI.addListener(m_handle, m_inst.getHandle(), eventKinds);
}
/**

View File

@@ -174,3 +174,15 @@ std::shared_ptr<INetworkClient> InstanceImpl::GetClient() {
std::scoped_lock lock{m_mutex};
return m_networkClient;
}
void InstanceImpl::Reset() {
std::scoped_lock lock{m_mutex};
m_networkServer.reset();
m_networkClient.reset();
m_servers.clear();
networkMode = NT_NET_MODE_NONE;
listenerStorage.Reset();
// connectionList should have been cleared by destroying networkClient/server
localStorage.Reset();
}

View File

@@ -56,6 +56,8 @@ class InstanceImpl {
std::shared_ptr<NetworkServer> GetServer();
std::shared_ptr<INetworkClient> GetClient();
void Reset();
ListenerStorage listenerStorage;
LoggerImpl logger_impl;
wpi::Logger logger;

View File

@@ -36,17 +36,16 @@ void ListenerStorage::Thread::Main() {
}
// call all the way back out to the C++ API to ensure valid handle
auto events = nt::ReadListenerQueue(m_poller);
if (events.empty()) {
continue;
}
std::unique_lock lock{m_mutex};
for (auto&& event : events) {
auto callbackIt = m_callbacks.find(event.listener);
if (callbackIt != m_callbacks.end()) {
auto callback = callbackIt->second;
lock.unlock();
callback(event);
lock.lock();
if (!events.empty()) {
std::unique_lock lock{m_mutex};
for (auto&& event : events) {
auto callbackIt = m_callbacks.find(event.listener);
if (callbackIt != m_callbacks.end()) {
auto callback = callbackIt->second;
lock.unlock();
callback(event);
lock.lock();
}
}
}
if (std::find(signaled.begin(), signaled.end(),
@@ -308,16 +307,32 @@ ListenerStorage::RemoveListener(NT_Listener listenerHandle) {
}
bool ListenerStorage::WaitForListenerQueue(double timeout) {
std::scoped_lock lock{m_mutex};
WPI_EventHandle h;
if (auto thr = m_thread.GetThread()) {
h = thr->m_waitQueueWaiter.GetHandle();
thr->m_waitQueueWakeup.Set();
} else {
return false;
{
std::scoped_lock lock{m_mutex};
if (auto thr = m_thread.GetThread()) {
h = thr->m_waitQueueWaiter.GetHandle();
thr->m_waitQueueWakeup.Set();
} else {
return false;
}
}
bool timedOut;
return wpi::WaitForObject(h, timeout, &timedOut);
wpi::WaitForObject(h, timeout, &timedOut);
return !timedOut;
}
void ListenerStorage::Reset() {
std::scoped_lock lock{m_mutex};
m_pollers.clear();
m_listeners.clear();
m_connListeners.clear();
m_topicListeners.clear();
m_valueListeners.clear();
m_logListeners.clear();
if (m_thread) {
m_thread.Stop();
}
}
std::vector<std::pair<NT_Listener, unsigned int>>

View File

@@ -59,6 +59,8 @@ class ListenerStorage final : public IListenerStorage {
bool WaitForListenerQueue(double timeout);
void Reset();
private:
// these assume the mutex is already held
NT_Listener DoAddListener(NT_ListenerPoller pollerHandle);

View File

@@ -20,11 +20,18 @@
#include "Log.h"
#include "PubSubOptions.h"
#include "Types_internal.h"
#include "Value_internal.h"
#include "networktables/NetworkTableValue.h"
#include "ntcore_c.h"
using namespace nt;
// maximum number of local publishers / subscribers to any given topic
static constexpr size_t kMaxPublishers = 512;
static constexpr size_t kMaxSubscribers = 512;
static constexpr size_t kMaxMultiSubscribers = 512;
static constexpr size_t kMaxListeners = 512;
namespace {
// Utility wrapper for making a set-like vector
@@ -253,8 +260,9 @@ struct LSImpl {
void CheckReset(TopicData* topic);
bool SetValue(TopicData* topic, const Value& value, unsigned int eventFlags);
void NotifyValue(TopicData* topic, unsigned int eventFlags);
bool SetValue(TopicData* topic, const Value& value, unsigned int eventFlags,
bool isDuplicate);
void NotifyValue(TopicData* topic, unsigned int eventFlags, bool isDuplicate);
void SetFlags(TopicData* topic, unsigned int flags);
void SetPersistent(TopicData* topic, bool value);
@@ -265,7 +273,7 @@ struct LSImpl {
unsigned int eventFlags, bool sendNetwork,
bool updateFlags = true);
void RefreshPubSubActive(TopicData* topic);
void RefreshPubSubActive(TopicData* topic, bool warnOnSubMismatch);
void NetworkAnnounce(TopicData* topic, std::string_view typeStr,
const wpi::json& properties, NT_Publisher pubHandle);
@@ -316,7 +324,8 @@ struct LSImpl {
PublisherData* PublishEntry(EntryData* entry, NT_Type type);
Value* GetSubEntryValue(NT_Handle subentryHandle);
bool PublishLocalValue(PublisherData* publisher, const Value& value);
bool PublishLocalValue(PublisherData* publisher, const Value& value,
bool force = false);
bool SetEntryValue(NT_Handle pubentryHandle, const Value& value);
bool SetDefaultEntryValue(NT_Handle pubsubentryHandle, const Value& value);
@@ -388,17 +397,9 @@ void PublisherData::UpdateActive() {
void SubscriberData::UpdateActive() {
// for subscribers, unassigned is a wildcard
// also allow numerically compatible subscribers
active =
config.type == NT_UNASSIGNED ||
(config.type == topic->type && config.typeStr == topic->typeStr) ||
((config.type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) != 0 &&
(config.type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) ==
(topic->type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE))) ||
((config.type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) !=
0 &&
(config.type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) ==
(topic->type &
(NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)));
active = config.type == NT_UNASSIGNED ||
(config.type == topic->type && config.typeStr == topic->typeStr) ||
IsNumericCompatible(config.type, topic->type);
}
void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) {
@@ -469,7 +470,7 @@ void LSImpl::CheckReset(TopicData* topic) {
}
bool LSImpl::SetValue(TopicData* topic, const Value& value,
unsigned int eventFlags) {
unsigned int eventFlags, bool isDuplicate) {
if (topic->type != NT_UNASSIGNED && topic->type != value.type()) {
return false;
}
@@ -480,9 +481,9 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
topic->type = value.type();
topic->lastValue = value;
topic->lastValueNetwork = isNetwork;
NotifyValue(topic, eventFlags);
NotifyValue(topic, eventFlags, isDuplicate);
}
if (topic->datalogType == value.type()) {
if (!isDuplicate && topic->datalogType == value.type()) {
for (auto&& datalog : topic->datalogs) {
datalog.Append(value);
}
@@ -490,20 +491,28 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
return true;
}
void LSImpl::NotifyValue(TopicData* topic, unsigned int eventFlags) {
void LSImpl::NotifyValue(TopicData* topic, unsigned int eventFlags,
bool isDuplicate) {
for (auto&& subscriber : topic->localSubscribers) {
if (subscriber->active) {
if (subscriber->active &&
(subscriber->config.keepDuplicates || !isDuplicate)) {
subscriber->pollStorage.emplace_back(topic->lastValue);
subscriber->handle.Set();
m_listenerStorage.Notify(subscriber->valueListeners, eventFlags,
topic->handle, 0, topic->lastValue);
if (!subscriber->valueListeners.empty()) {
m_listenerStorage.Notify(subscriber->valueListeners, eventFlags,
topic->handle, 0, topic->lastValue);
}
}
}
for (auto&& subscriber : topic->multiSubscribers) {
subscriber->handle.Set();
m_listenerStorage.Notify(subscriber->valueListeners, eventFlags,
topic->handle, 0, topic->lastValue);
if (subscriber->options.keepDuplicates || !isDuplicate) {
subscriber->handle.Set();
if (!subscriber->valueListeners.empty()) {
m_listenerStorage.Notify(subscriber->valueListeners, eventFlags,
topic->handle, 0, topic->lastValue);
}
}
}
}
@@ -610,12 +619,19 @@ void LSImpl::PropertiesUpdated(TopicData* topic, const wpi::json& update,
}
}
void LSImpl::RefreshPubSubActive(TopicData* topic) {
void LSImpl::RefreshPubSubActive(TopicData* topic, bool warnOnSubMismatch) {
for (auto&& publisher : topic->localPublishers) {
publisher->UpdateActive();
}
for (auto&& subscriber : topic->localSubscribers) {
subscriber->UpdateActive();
if (warnOnSubMismatch && topic->Exists() && !subscriber->active) {
// warn on type mismatch
INFO(
"local subscribe to '{}' disabled due to type mismatch (wanted '{}', "
"published as '{}')",
topic->name, subscriber->config.typeStr, topic->typeStr);
}
}
}
@@ -643,7 +659,7 @@ void LSImpl::NetworkAnnounce(TopicData* topic, std::string_view typeStr,
}
topic->type = type;
topic->typeStr = typeStr;
RefreshPubSubActive(topic);
RefreshPubSubActive(topic, true);
}
if (!didExist) {
event |= NT_EVENT_PUBLISH;
@@ -692,7 +708,7 @@ void LSImpl::RemoveNetworkPublisher(TopicData* topic) {
nextPub->config.typeStr != topic->typeStr) {
topic->type = nextPub->config.type;
topic->typeStr = nextPub->config.typeStr;
RefreshPubSubActive(topic);
RefreshPubSubActive(topic, false);
// this may result in a duplicate publish warning on the server side,
// but send one anyway in this case just to be sure
if (nextPub->active && m_network) {
@@ -720,19 +736,20 @@ PublisherData* LSImpl::AddLocalPublisher(TopicData* topic,
topic->localPublishers.Add(publisher);
if (!didExist) {
DEBUG4("AddLocalPublisher: setting {} type {} typestr {}", topic->name,
static_cast<int>(config.type), config.typeStr);
// set the type to the published type
topic->type = config.type;
topic->typeStr = config.typeStr;
RefreshPubSubActive(topic);
RefreshPubSubActive(topic, true);
if (properties.is_null()) {
topic->properties = wpi::json::object();
} else if (properties.is_object()) {
topic->properties = properties;
} else {
WPI_WARNING(m_logger,
"ignoring non-object properties when publishing '{}'",
topic->name);
WARNING("ignoring non-object properties when publishing '{}'",
topic->name);
topic->properties = wpi::json::object();
}
@@ -784,7 +801,7 @@ std::unique_ptr<PublisherData> LSImpl::RemoveLocalPublisher(
nextPub->config.typeStr != topic->typeStr) {
topic->type = nextPub->config.type;
topic->typeStr = nextPub->config.typeStr;
RefreshPubSubActive(topic);
RefreshPubSubActive(topic, false);
if (nextPub->active && m_network) {
m_network->Publish(nextPub->handle, topic->handle, topic->name,
topic->typeStr, topic->properties,
@@ -807,7 +824,7 @@ SubscriberData* LSImpl::AddLocalSubscriber(TopicData* topic,
// warn on type mismatch
INFO(
"local subscribe to '{}' disabled due to type mismatch (wanted '{}', "
"currently '{}')",
"published as '{}')",
topic->name, config.typeStr, topic->typeStr);
}
if (m_network) {
@@ -889,6 +906,12 @@ std::unique_ptr<MultiSubscriberData> LSImpl::RemoveMultiSubscriber(
void LSImpl::AddListenerImpl(NT_Listener listenerHandle, TopicData* topic,
unsigned int eventMask) {
if (topic->localSubscribers.size() >= kMaxSubscribers) {
ERROR(
"reached maximum number of subscribers to '{}', ignoring listener add",
topic->name);
return;
}
// subscribe to make sure topic updates are received
PubSubConfig config;
config.topicsOnly = (eventMask & NT_EVENT_VALUE_ALL) == 0;
@@ -906,6 +929,12 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
auto topic = subscriber->topic;
if ((eventMask & NT_EVENT_TOPIC) != 0) {
if (topic->listeners.size() >= kMaxListeners) {
ERROR("reached maximum number of listeners to '{}', not adding listener",
topic->name);
return;
}
m_listenerStorage.Activate(
listenerHandle, eventMask & (NT_EVENT_TOPIC | NT_EVENT_IMMEDIATE));
@@ -922,6 +951,11 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
}
if ((eventMask & NT_EVENT_VALUE_ALL) != 0) {
if (subscriber->valueListeners.size() >= kMaxListeners) {
ERROR("reached maximum number of listeners to '{}', not adding listener",
topic->name);
return;
}
m_listenerStorage.Activate(
listenerHandle, eventMask & (NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE),
[subentryHandle](unsigned int mask, Event* event) {
@@ -968,6 +1002,11 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
}
if ((eventMask & NT_EVENT_TOPIC) != 0) {
if (m_topicPrefixListeners.size() >= kMaxListeners) {
ERROR("reached maximum number of listeners, not adding listener");
return;
}
m_listenerStorage.Activate(
listenerHandle, eventMask & (NT_EVENT_TOPIC | NT_EVENT_IMMEDIATE));
@@ -989,6 +1028,11 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
}
if ((eventMask & NT_EVENT_VALUE_ALL) != 0) {
if (subscriber->valueListeners.size() >= kMaxListeners) {
ERROR("reached maximum number of listeners, not adding listener");
return;
}
m_listenerStorage.Activate(
listenerHandle, eventMask & (NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE),
[subentryHandle = subscriber->handle.GetHandle()](unsigned int mask,
@@ -1018,6 +1062,10 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
void LSImpl::AddListener(NT_Listener listenerHandle,
std::span<const std::string_view> prefixes,
unsigned int eventMask) {
if (m_multiSubscribers.size() >= kMaxMultiSubscribers) {
ERROR("reached maximum number of multi-subscribers, not adding listener");
return;
}
// subscribe to make sure topic updates are received
PubSubOptions options;
options.topicsOnly = (eventMask & NT_EVENT_VALUE_ALL) == 0;
@@ -1135,8 +1183,12 @@ PublisherData* LSImpl::PublishEntry(EntryData* entry, NT_Type type) {
entry->subscriber->config.typeStr = typeStr;
} else if (entry->subscriber->config.type != type ||
entry->subscriber->config.typeStr != typeStr) {
// don't allow dynamically changing the type of an entry
return nullptr;
if (!IsNumericCompatible(type, entry->subscriber->config.type)) {
// don't allow dynamically changing the type of an entry
ERROR("cannot publish entry {} as type {}, previously subscribed as {}",
entry->topic->name, typeStr, entry->subscriber->config.typeStr);
return nullptr;
}
}
// create publisher
entry->publisher = AddLocalPublisher(entry->topic, wpi::json::object(),
@@ -1152,19 +1204,30 @@ Value* LSImpl::GetSubEntryValue(NT_Handle subentryHandle) {
}
}
bool LSImpl::PublishLocalValue(PublisherData* publisher, const Value& value) {
bool LSImpl::PublishLocalValue(PublisherData* publisher, const Value& value,
bool force) {
if (!value) {
return false;
}
if (publisher->topic->type != NT_UNASSIGNED &&
publisher->topic->type != value.type()) {
if (IsNumericCompatible(publisher->topic->type, value.type())) {
return PublishLocalValue(
publisher, ConvertNumericValue(value, publisher->topic->type));
}
return false;
}
if (publisher->active) {
if (m_network) {
bool isDuplicate;
if (force || publisher->config.keepDuplicates) {
isDuplicate = false;
} else {
isDuplicate = (publisher->topic->lastValue == value);
}
if (!isDuplicate && m_network) {
m_network->SetValue(publisher->handle, value);
}
return SetValue(publisher->topic, value, NT_EVENT_VALUE_LOCAL);
return SetValue(publisher->topic, value, NT_EVENT_VALUE_LOCAL, isDuplicate);
} else {
return false;
}
@@ -1188,28 +1251,38 @@ bool LSImpl::SetEntryValue(NT_Handle pubentryHandle, const Value& value) {
bool LSImpl::SetDefaultEntryValue(NT_Handle pubsubentryHandle,
const Value& value) {
DEBUG4("SetDefaultEntryValue({}, {})", pubsubentryHandle,
static_cast<int>(value.type()));
if (!value) {
return false;
}
if (auto topic = GetTopic(pubsubentryHandle)) {
if (topic->type == NT_UNASSIGNED || topic->type == value.type()) {
// set without notifying
topic->type = value.type();
topic->lastValue = value;
topic->lastValue.SetTime(0);
topic->lastValue.SetServerTime(0);
if (!topic->lastValue &&
(topic->type == NT_UNASSIGNED || topic->type == value.type() ||
IsNumericCompatible(topic->type, value.type()))) {
// publish if we haven't yet
auto publisher = m_publishers.Get(pubsubentryHandle);
if (!publisher) {
if (auto entry = m_entries.Get(pubsubentryHandle)) {
publisher = PublishEntry(entry, value.type());
}
if (!publisher) {
return true;
}
}
if (publisher->active && m_network) {
m_network->SetValue(publisher->handle, value);
// force value timestamps to 0
if (topic->type == NT_UNASSIGNED) {
topic->type = value.type();
}
if (topic->type == value.type()) {
topic->lastValue = value;
} else if (IsNumericCompatible(topic->type, value.type())) {
topic->lastValue = ConvertNumericValue(value, topic->type);
} else {
return true;
}
topic->lastValue.SetTime(0);
topic->lastValue.SetServerTime(0);
if (publisher) {
PublishLocalValue(publisher, topic->lastValue, true);
}
return true;
}
@@ -1273,7 +1346,8 @@ void LocalStorage::NetworkPropertiesUpdate(std::string_view name,
void LocalStorage::NetworkSetValue(NT_Topic topicHandle, const Value& value) {
std::scoped_lock lock{m_mutex};
if (auto topic = m_impl->m_topics.Get(topicHandle)) {
m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE);
m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE,
value == topic->lastValue);
}
}
@@ -1548,6 +1622,13 @@ NT_Subscriber LocalStorage::Subscribe(NT_Topic topicHandle, NT_Type type,
return 0;
}
if (topic->localSubscribers.size() >= kMaxSubscribers) {
WPI_ERROR(m_impl->m_logger,
"reached maximum number of subscribers to '{}', not subscribing",
topic->name);
return 0;
}
// Create subscriber
return m_impl->AddLocalSubscriber(topic, PubSubConfig{type, typeStr, options})
->handle;
@@ -1562,6 +1643,13 @@ NT_MultiSubscriber LocalStorage::SubscribeMultiple(
std::span<const std::string_view> prefixes,
std::span<const PubSubOption> options) {
std::scoped_lock lock{m_mutex};
if (m_impl->m_multiSubscribers.size() >= kMaxMultiSubscribers) {
WPI_ERROR(m_impl->m_logger,
"reached maximum number of multi-subscribers, not subscribing");
return 0;
}
PubSubOptions opts{options};
opts.prefixMatch = true;
return m_impl->AddMultiSubscriber(prefixes, opts)->handle;
@@ -1594,6 +1682,13 @@ NT_Publisher LocalStorage::Publish(NT_Topic topicHandle, NT_Type type,
return 0;
}
if (topic->localPublishers.size() >= kMaxPublishers) {
WPI_ERROR(m_impl->m_logger,
"reached maximum number of publishers to '{}', not publishing",
topic->name);
return 0;
}
return m_impl
->AddLocalPublisher(topic, properties,
PubSubConfig{type, typeStr, options})
@@ -1627,6 +1722,14 @@ NT_Entry LocalStorage::GetEntry(NT_Topic topicHandle, NT_Type type,
return 0;
}
if (topic->localSubscribers.size() >= kMaxSubscribers) {
WPI_ERROR(
m_impl->m_logger,
"reached maximum number of subscribers to '{}', not creating entry",
topic->name);
return 0;
}
// Create subscriber
auto subscriber =
m_impl->AddLocalSubscriber(topic, PubSubConfig{type, typeStr, options});
@@ -1977,10 +2080,17 @@ READ_QUEUE_NUMBER(Double)
Value LocalStorage::GetEntryValue(NT_Handle subentryHandle) {
std::scoped_lock lock{m_mutex};
if (auto subscriber = m_impl->GetSubEntry(subentryHandle)) {
return subscriber->topic->lastValue;
} else {
return {};
if (subscriber->config.type == NT_UNASSIGNED ||
!subscriber->topic->lastValue ||
subscriber->config.type == subscriber->topic->lastValue.type()) {
return subscriber->topic->lastValue;
} else if (IsNumericCompatible(subscriber->config.type,
subscriber->topic->lastValue.type())) {
return ConvertNumericValue(subscriber->topic->lastValue,
subscriber->config.type);
}
}
return {};
}
void LocalStorage::SetEntryFlags(NT_Entry entryHandle, unsigned int flags) {
@@ -2010,6 +2120,14 @@ NT_Entry LocalStorage::GetEntry(std::string_view name) {
auto* topic = m_impl->GetOrCreateTopic(name);
if (topic->entry == 0) {
if (topic->localSubscribers.size() >= kMaxSubscribers) {
WPI_ERROR(
m_impl->m_logger,
"reached maximum number of subscribers to '{}', not creating entry",
topic->name);
return 0;
}
// Create subscriber
auto* subscriber = m_impl->AddLocalSubscriber(topic, {});
@@ -2111,3 +2229,9 @@ void LocalStorage::StopDataLog(NT_DataLogger logger) {
}
}
}
void LocalStorage::Reset() {
std::scoped_lock lock{m_mutex};
m_impl = std::make_unique<Impl>(m_impl->m_inst, m_impl->m_listenerStorage,
m_impl->m_logger);
}

View File

@@ -209,6 +209,8 @@ class LocalStorage final : public net::ILocalStorage {
std::string_view logPrefix);
void StopDataLog(NT_DataLogger logger);
void Reset();
private:
class Impl;
std::unique_ptr<Impl> m_impl;

View File

@@ -111,6 +111,7 @@ class NSImpl {
void HandleLocal();
void LoadPersistent();
void SavePersistent(std::string_view filename, std::string_view data);
void Init();
void AddConnection(ServerConnection* conn, const ConnectionInfo& info);
void RemoveConnection(ServerConnection* conn);
@@ -372,11 +373,16 @@ void NSImpl::LoadPersistent() {
}
}
static void SavePersistent(std::string_view filename, std::string_view data) {
void NSImpl::SavePersistent(std::string_view filename, std::string_view data) {
// write to temporary file
auto tmp = fmt::format("{}.tmp", filename);
std::error_code ec;
wpi::raw_fd_ostream os{tmp, ec, fs::F_Text};
if (ec.value() != 0) {
INFO("could not open persistent file '{}' for write: {}", tmp,
ec.message());
return;
}
os << data;
os.close();
if (os.has_error()) {
@@ -414,9 +420,8 @@ void NSImpl::Init() {
if (m_serverImpl.PersistentChanged()) {
uv::QueueWork(
m_loop,
[fn = m_persistentFilename, data = m_serverImpl.DumpPersistent()] {
SavePersistent(fn, data);
},
[this, fn = m_persistentFilename,
data = m_serverImpl.DumpPersistent()] { SavePersistent(fn, data); },
nullptr);
}
});

View File

@@ -14,4 +14,17 @@ std::string_view TypeToString(NT_Type type);
NT_Type StringToType(std::string_view typeStr);
NT_Type StringToType3(std::string_view typeStr);
constexpr bool IsNumeric(NT_Type type) {
return (type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) != 0;
}
constexpr bool IsNumericArray(NT_Type type) {
return (type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) != 0;
}
constexpr bool IsNumericCompatible(NT_Type a, NT_Type b) {
return (IsNumeric(a) && IsNumeric(b)) ||
(IsNumericArray(a) && IsNumericArray(b));
}
} // namespace nt

View File

@@ -89,6 +89,15 @@ Value Value::MakeBooleanArray(std::span<const int> value, int64_t time) {
return val;
}
Value Value::MakeBooleanArray(std::vector<int>&& value, int64_t time) {
Value val{NT_BOOLEAN_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<int>>(std::move(value));
val.m_val.data.arr_boolean.arr = data->data();
val.m_val.data.arr_boolean.size = data->size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeIntegerArray(std::span<const int64_t> value, int64_t time) {
Value val{NT_INTEGER_ARRAY, time, private_init{}};
auto data =
@@ -99,6 +108,15 @@ Value Value::MakeIntegerArray(std::span<const int64_t> value, int64_t time) {
return val;
}
Value Value::MakeIntegerArray(std::vector<int64_t>&& value, int64_t time) {
Value val{NT_INTEGER_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<int64_t>>(std::move(value));
val.m_val.data.arr_int.arr = data->data();
val.m_val.data.arr_int.size = data->size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeFloatArray(std::span<const float> value, int64_t time) {
Value val{NT_FLOAT_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<float>>(value.begin(), value.end());
@@ -108,6 +126,15 @@ Value Value::MakeFloatArray(std::span<const float> value, int64_t time) {
return val;
}
Value Value::MakeFloatArray(std::vector<float>&& value, int64_t time) {
Value val{NT_FLOAT_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<float>>(std::move(value));
val.m_val.data.arr_float.arr = data->data();
val.m_val.data.arr_float.size = data->size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeDoubleArray(std::span<const double> value, int64_t time) {
Value val{NT_DOUBLE_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<double>>(value.begin(), value.end());
@@ -117,6 +144,15 @@ Value Value::MakeDoubleArray(std::span<const double> value, int64_t time) {
return val;
}
Value Value::MakeDoubleArray(std::vector<double>&& value, int64_t time) {
Value val{NT_DOUBLE_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<double>>(std::move(value));
val.m_val.data.arr_double.arr = data->data();
val.m_val.data.arr_double.size = data->size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeStringArray(std::span<const std::string> value, int64_t time) {
Value val{NT_STRING_ARRAY, time, private_init{}};
auto data = std::make_shared<StringArrayStorage>(value);

View File

@@ -0,0 +1,49 @@
// 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.
#include "Value_internal.h"
using namespace nt;
Value nt::ConvertNumericValue(const Value& value, NT_Type type) {
switch (type) {
case NT_INTEGER: {
Value newval =
Value::MakeInteger(GetNumericAs<int64_t>(value), value.time());
newval.SetServerTime(value.server_time());
return newval;
}
case NT_FLOAT: {
Value newval = Value::MakeFloat(GetNumericAs<float>(value), value.time());
newval.SetServerTime(value.server_time());
return newval;
}
case NT_DOUBLE: {
Value newval =
Value::MakeDouble(GetNumericAs<double>(value), value.time());
newval.SetServerTime(value.server_time());
return newval;
}
case NT_INTEGER_ARRAY: {
Value newval = Value::MakeIntegerArray(GetNumericArrayAs<int64_t>(value),
value.time());
newval.SetServerTime(value.server_time());
return newval;
}
case NT_FLOAT_ARRAY: {
Value newval =
Value::MakeFloatArray(GetNumericArrayAs<float>(value), value.time());
newval.SetServerTime(value.server_time());
return newval;
}
case NT_DOUBLE_ARRAY: {
Value newval = Value::MakeDoubleArray(GetNumericArrayAs<double>(value),
value.time());
newval.SetServerTime(value.server_time());
return newval;
}
default:
return {};
}
}

View File

@@ -12,6 +12,7 @@
#include <wpi/MemAlloc.h>
#include "networktables/NetworkTableValue.h"
#include "ntcore_c.h"
namespace nt {
@@ -56,4 +57,35 @@ O* ConvertToC(const std::basic_string<I>& in, size_t* out_len) {
return out;
}
template <typename T>
T GetNumericAs(const Value& value) {
if (value.IsInteger()) {
return static_cast<T>(value.GetInteger());
} else if (value.IsFloat()) {
return static_cast<T>(value.GetFloat());
} else if (value.IsDouble()) {
return static_cast<T>(value.GetDouble());
} else {
return {};
}
}
template <typename T>
std::vector<T> GetNumericArrayAs(const Value& value) {
if (value.IsIntegerArray()) {
auto arr = value.GetIntegerArray();
return {arr.begin(), arr.end()};
} else if (value.IsFloatArray()) {
auto arr = value.GetFloatArray();
return {arr.begin(), arr.end()};
} else if (value.IsDoubleArray()) {
auto arr = value.GetDoubleArray();
return {arr.begin(), arr.end()};
} else {
return {};
}
}
Value ConvertNumericValue(const Value& value, NT_Type type);
} // namespace nt

View File

@@ -620,8 +620,20 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
sub->periodMs = kMinPeriodMs;
}
// update periodic sender (if not local)
if (!m_local) {
if (m_periodMs == UINT32_MAX) {
m_periodMs = sub->periodMs;
} else {
m_periodMs = std::gcd(m_periodMs, sub->periodMs);
}
if (m_periodMs < kMinPeriodMs) {
m_periodMs = kMinPeriodMs;
}
m_setPeriodic(m_periodMs);
}
// see if this immediately subscribes to any topics
bool updatedPeriodic = false;
for (auto&& topic : m_server.m_topics) {
bool removed = false;
if (replace) {
@@ -647,14 +659,6 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
m_server.UpdateMetaTopicSub(topic.get());
}
if (added || removed) {
// update periodic sender (if not local)
if (!m_local) {
m_periodMs = std::gcd(m_periodMs, sub->periodMs);
updatedPeriodic = true;
}
}
if (!wasSubscribed && added && !removed) {
// announce topic to client
DEBUG4("client {}: announce {}", m_id, topic->name);
@@ -667,12 +671,6 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
}
}
}
if (updatedPeriodic) {
if (m_periodMs < kMinPeriodMs) {
m_periodMs = kMinPeriodMs;
}
m_setPeriodic(m_periodMs);
}
// update meta data
UpdateMetaClientSub();

View File

@@ -101,9 +101,44 @@ void NetworkTableInstance::SetServer(std::span<const std::string_view> servers,
SetServer(serversArr);
}
NT_Listener NetworkTableInstance::AddListener(MultiSubscriber& subscriber,
int eventMask,
NT_Listener NetworkTableInstance::AddListener(Topic topic,
unsigned int eventMask,
ListenerCallback listener) {
if (::nt::GetInstanceFromHandle(topic.GetHandle()) != m_handle) {
fmt::print(stderr, "AddListener: topic is not from this instance\n");
return 0;
}
return ::nt::AddListener(topic.GetHandle(), eventMask, std::move(listener));
}
NT_Listener NetworkTableInstance::AddListener(Subscriber& subscriber,
unsigned int eventMask,
ListenerCallback listener) {
if (::nt::GetInstanceFromHandle(subscriber.GetHandle()) != m_handle) {
fmt::print(stderr, "AddListener: subscriber is not from this instance\n");
return 0;
}
return ::nt::AddListener(subscriber.GetHandle(), eventMask,
std::move(listener));
}
NT_Listener NetworkTableInstance::AddListener(NetworkTableEntry& entry,
int eventMask,
ListenerCallback listener) {
if (::nt::GetInstanceFromHandle(entry.GetHandle()) != m_handle) {
fmt::print(stderr, "AddListener: entry is not from this instance\n");
return 0;
}
return ::nt::AddListener(entry.GetHandle(), eventMask, std::move(listener));
}
NT_Listener NetworkTableInstance::AddListener(MultiSubscriber& subscriber,
int eventMask,
ListenerCallback listener) {
if (::nt::GetInstanceFromHandle(subscriber.GetHandle()) != m_handle) {
fmt::print(stderr, "AddListener: subscriber is not from this instance\n");
return 0;
}
return ::nt::AddListener(subscriber.GetHandle(), eventMask,
std::move(listener));
}

View File

@@ -45,6 +45,12 @@ NT_Inst CreateInstance() {
return Handle{InstanceImpl::Alloc(), 0, Handle::kInstance};
}
void ResetInstance(NT_Inst inst) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->Reset();
}
}
void DestroyInstance(NT_Inst inst) {
int i = Handle{inst}.GetTypedInst(Handle::kInstance);
if (i < 0) {
@@ -497,6 +503,14 @@ NT_Listener AddPolledListener(NT_ListenerPoller poller,
NT_Listener AddPolledListener(NT_ListenerPoller poller, NT_Handle handle,
unsigned int mask) {
if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) {
if (Handle{handle}.GetInst() != Handle{poller}.GetInst()) {
WPI_ERROR(
ii->logger,
"AddPolledListener(): trying to listen to handle {} (instance {}) "
"with poller {} (instance {}), ignored due to different instance",
handle, Handle{handle}.GetInst(), poller, Handle{poller}.GetInst());
return {};
}
auto listener = ii->listenerStorage.AddListener(poller);
DoAddListener(*ii, listener, handle, mask);
return listener;

View File

@@ -132,7 +132,7 @@ class NetworkTableInstance final {
*
* @param inst Instance
*/
static void Destroy(NetworkTableInstance inst);
static void Destroy(NetworkTableInstance& inst);
/**
* Gets the native handle for the entry.

View File

@@ -27,9 +27,10 @@ inline NetworkTableInstance NetworkTableInstance::Create() {
return NetworkTableInstance{CreateInstance()};
}
inline void NetworkTableInstance::Destroy(NetworkTableInstance inst) {
inline void NetworkTableInstance::Destroy(NetworkTableInstance& inst) {
if (inst.m_handle != 0) {
DestroyInstance(inst.m_handle);
inst.m_handle = 0;
}
}
@@ -99,22 +100,6 @@ inline NT_Listener NetworkTableInstance::AddConnectionListener(
std::move(callback));
}
inline NT_Listener NetworkTableInstance::AddListener(
Topic topic, unsigned int eventMask, ListenerCallback listener) {
return ::nt::AddListener(topic.GetHandle(), eventMask, std::move(listener));
}
inline NT_Listener NetworkTableInstance::AddListener(
Subscriber& subscriber, unsigned int eventMask, ListenerCallback listener) {
return ::nt::AddListener(subscriber.GetHandle(), eventMask,
std::move(listener));
}
inline NT_Listener NetworkTableInstance::AddListener(
NetworkTableEntry& entry, int eventMask, ListenerCallback listener) {
return ::nt::AddListener(entry.GetHandle(), eventMask, std::move(listener));
}
inline NT_Listener NetworkTableInstance::AddListener(
std::span<const std::string_view> prefixes, int eventMask,
ListenerCallback listener) {

View File

@@ -72,7 +72,11 @@ inline NetworkTableListener::NetworkTableListener(NetworkTableListener&& rhs)
inline NetworkTableListener& NetworkTableListener::operator=(
NetworkTableListener&& rhs) {
std::swap(m_handle, rhs.m_handle);
if (m_handle != 0) {
nt::RemoveListener(m_handle);
}
m_handle = rhs.m_handle;
rhs.m_handle = 0;
return *this;
}
@@ -102,7 +106,11 @@ inline NetworkTableListenerPoller::NetworkTableListenerPoller(
inline NetworkTableListenerPoller& NetworkTableListenerPoller::operator=(
NetworkTableListenerPoller&& rhs) {
std::swap(m_handle, rhs.m_handle);
if (m_handle != 0) {
nt::DestroyListenerPoller(m_handle);
}
m_handle = rhs.m_handle;
rhs.m_handle = 0;
return *this;
}

View File

@@ -471,6 +471,18 @@ class Value final {
return MakeBooleanArray(std::span(value.begin(), value.end()), time);
}
/**
* Creates a boolean array entry value.
*
* @param value the value
* @param time if nonzero, the creation time to use (instead of the current
* time)
* @return The entry value
*
* @note This function moves the values out of the vector.
*/
static Value MakeBooleanArray(std::vector<int>&& value, int64_t time = 0);
/**
* Creates an integer array entry value.
*
@@ -495,6 +507,18 @@ class Value final {
return MakeIntegerArray(std::span(value.begin(), value.end()), time);
}
/**
* Creates an integer array entry value.
*
* @param value the value
* @param time if nonzero, the creation time to use (instead of the current
* time)
* @return The entry value
*
* @note This function moves the values out of the vector.
*/
static Value MakeIntegerArray(std::vector<int64_t>&& value, int64_t time = 0);
/**
* Creates a float array entry value.
*
@@ -518,6 +542,18 @@ class Value final {
return MakeFloatArray(std::span(value.begin(), value.end()), time);
}
/**
* Creates a float array entry value.
*
* @param value the value
* @param time if nonzero, the creation time to use (instead of the current
* time)
* @return The entry value
*
* @note This function moves the values out of the vector.
*/
static Value MakeFloatArray(std::vector<float>&& value, int64_t time = 0);
/**
* Creates a double array entry value.
*
@@ -541,6 +577,18 @@ class Value final {
return MakeDoubleArray(std::span(value.begin(), value.end()), time);
}
/**
* Creates a double array entry value.
*
* @param value the value
* @param time if nonzero, the creation time to use (instead of the current
* time)
* @return The entry value
*
* @note This function moves the values out of the vector.
*/
static Value MakeDoubleArray(std::vector<double>&& value, int64_t time = 0);
/**
* Creates a string array entry value.
*

View File

@@ -223,6 +223,14 @@ class Event {
*/
unsigned int flags{0};
/**
* Test event flags.
*
* @param kind event flag(s) to test
* @return True if flags matches kind
*/
bool Is(unsigned int kind) const { return (flags & kind) != 0; }
/** Event data; content depends on flags. */
std::variant<ConnectionInfo, TopicInfo, ValueEventData, LogMessage> data;
@@ -340,6 +348,13 @@ NT_Inst GetDefaultInstance();
*/
NT_Inst CreateInstance();
/**
* Reset the internals of an instance. Every handle previously associated
* with this instance will no longer be valid, except for the instance
* handle.
*/
void ResetInstance(NT_Inst inst);
/**
* Destroy an instance.
* The default instance cannot be destroyed.

View File

@@ -8,10 +8,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import edu.wpi.first.util.WPIUtilJNI;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -64,7 +66,7 @@ class ConnectionListenerTest {
assertNotSame(poller, 0, "bad poller handle");
int handle =
NetworkTablesJNI.addListener(
poller, m_serverInst.getHandle(), NetworkTableEvent.kConnection);
poller, m_serverInst.getHandle(), EnumSet.of(NetworkTableEvent.Kind.kConnection));
assertNotSame(handle, 0, "bad listener handle");
// trigger a connect event
@@ -82,7 +84,7 @@ class ConnectionListenerTest {
assertEquals(1, events.length);
assertEquals(handle, events[0].listener);
assertNotNull(events[0].connInfo);
assertEquals(events[0].flags, NetworkTableEvent.kConnected);
assertTrue(events[0].is(NetworkTableEvent.Kind.kConnected));
// trigger a disconnect event
m_clientInst.stopClient();
@@ -103,7 +105,7 @@ class ConnectionListenerTest {
assertNotNull(events);
assertEquals(1, events.length);
assertEquals(handle, events[0].listener);
assertEquals(events[0].flags, NetworkTableEvent.kDisconnected);
assertTrue(events[0].is(NetworkTableEvent.Kind.kDisconnected));
}
private static int threadedPort = 10001;
@@ -155,7 +157,7 @@ class ConnectionListenerTest {
assertEquals(1, events.size());
assertEquals(handle, events.get(0).listener);
assertNotNull(events.get(0).connInfo);
assertEquals(events.get(0).flags, NetworkTableEvent.kConnected);
assertTrue(events.get(0).is(NetworkTableEvent.Kind.kConnected));
events.clear();
}
@@ -180,7 +182,7 @@ class ConnectionListenerTest {
assertEquals(1, events.size());
assertEquals(handle, events.get(0).listener);
assertNotNull(events.get(0).connInfo);
assertEquals(events.get(0).flags, NetworkTableEvent.kDisconnected);
assertTrue(events.get(0).is(NetworkTableEvent.Kind.kDisconnected));
}
}
}

View File

@@ -37,7 +37,7 @@ class LoggerTest {
// wait for client to report it's started, then wait another 0.1 sec
try {
int count = 0;
while ((m_clientInst.getNetworkMode() & NetworkTableInstance.kNetModeClient4) == 0) {
while (!m_clientInst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.kClient4)) {
Thread.sleep(100);
count++;
if (count > 30) {

View File

@@ -6,9 +6,11 @@ package edu.wpi.first.networktables;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import edu.wpi.first.util.WPIUtilJNI;
import java.util.EnumSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -37,7 +39,8 @@ class TopicListenerTest {
// Use connection listener to ensure we've connected
int poller = NetworkTablesJNI.createListenerPoller(m_clientInst.getHandle());
NetworkTablesJNI.addListener(poller, m_clientInst.getHandle(), NetworkTableEvent.kConnected);
NetworkTablesJNI.addListener(
poller, m_clientInst.getHandle(), EnumSet.of(NetworkTableEvent.Kind.kConnected));
try {
if (WPIUtilJNI.waitForObjectTimeout(poller, 1.0)) {
fail("client didn't connect to server");
@@ -55,7 +58,8 @@ class TopicListenerTest {
connect();
final int poller = NetworkTablesJNI.createListenerPoller(m_serverInst.getHandle());
final int handle =
NetworkTablesJNI.addListener(poller, new String[] {"/foo"}, NetworkTableEvent.kPublish);
NetworkTablesJNI.addListener(
poller, new String[] {"/foo"}, EnumSet.of(NetworkTableEvent.Kind.kPublish));
// Trigger an event
m_clientInst.getEntry("/foo/bar").setDouble(1.0);
@@ -83,6 +87,6 @@ class TopicListenerTest {
assertNotNull(events[0].topicInfo);
assertEquals(m_serverInst.getTopic("/foo/bar"), events[0].topicInfo.getTopic());
assertEquals("/foo/bar", events[0].topicInfo.name);
assertEquals(NetworkTableEvent.kPublish, events[0].flags);
assertTrue(events[0].is(NetworkTableEvent.Kind.kPublish));
}
}

View File

@@ -13,21 +13,26 @@
#include "gtest/gtest.h"
#include "net/MockNetworkInterface.h"
#include "ntcore_c.h"
#include "ntcore_cpp.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Property;
using ::testing::Return;
namespace nt {
::testing::Matcher<const PubSubOptions&> IsPubSubOptions(
const PubSubOptions& good) {
return ::testing::AllOf(
::testing::Field("periodic", &PubSubOptions::periodic, good.periodic),
::testing::Field("pollStorageSize", &PubSubOptions::pollStorageSize,
good.pollStorageSize),
::testing::Field("logging", &PubSubOptions::sendAll, good.sendAll),
::testing::Field("keepDuplicates", &PubSubOptions::keepDuplicates,
good.keepDuplicates));
return AllOf(Field("periodic", &PubSubOptions::periodic, good.periodic),
Field("pollStorageSize", &PubSubOptions::pollStorageSize,
good.pollStorageSize),
Field("logging", &PubSubOptions::sendAll, good.sendAll),
Field("keepDuplicates", &PubSubOptions::keepDuplicates,
good.keepDuplicates));
}
class LocalStorageTest : public ::testing::Test {
@@ -158,7 +163,7 @@ TEST_F(LocalStorageTest, SubscribeNoTypeLocalPubPost) {
EXPECT_EQ(vals[0].value, true);
EXPECT_EQ(vals[0].time, 5);
val = Value::MakeBoolean(true, 6);
val = Value::MakeBoolean(false, 6);
EXPECT_CALL(network, SetValue(pub, val));
storage.SetEntryValue(pub, val);
@@ -221,7 +226,7 @@ TEST_F(LocalStorageTest, EntryNoTypeLocalSet) {
EXPECT_EQ(vals[0].time, 5);
// normal set with same type
val = Value::MakeBoolean(true, 6);
val = Value::MakeBoolean(false, 6);
EXPECT_CALL(network, SetValue(_, val));
EXPECT_TRUE(storage.SetEntryValue(entry, val));
@@ -247,6 +252,9 @@ TEST_F(LocalStorageTest, PubUnpubPub) {
EXPECT_CALL(network, Publish(_, fooTopic, std::string_view{"foo"},
std::string_view{"boolean"}, wpi::json::object(),
IsPubSubOptions({})));
EXPECT_CALL(logger, Call(NT_LOG_INFO, _, _,
"local subscribe to 'foo' disabled due to type "
"mismatch (wanted 'int', published as 'boolean')"));
auto pub = storage.Publish(fooTopic, NT_BOOLEAN, "boolean", {}, {});
auto val = Value::MakeBoolean(true, 5);
@@ -329,7 +337,7 @@ TEST_F(LocalStorageTest, LocalSubConflict) {
IsPubSubOptions({})));
EXPECT_CALL(logger, Call(NT_LOG_INFO, _, _,
"local subscribe to 'foo' disabled due to type "
"mismatch (wanted 'int', currently 'boolean')"));
"mismatch (wanted 'int', published as 'boolean')"));
storage.Subscribe(fooTopic, NT_INTEGER, "int", {});
}
@@ -466,6 +474,12 @@ TEST_F(LocalStorageTest, SetValueEmptyUntypedEntry) {
}
TEST_F(LocalStorageTest, PublishUntyped) {
EXPECT_CALL(
logger,
Call(
NT_LOG_ERROR, _, _,
"cannot publish 'foo' with an unassigned type or empty type string"));
EXPECT_EQ(storage.Publish(fooTopic, NT_UNASSIGNED, "", {}, {}), 0u);
}
@@ -473,4 +487,306 @@ TEST_F(LocalStorageTest, SetValueInvalidHandle) {
EXPECT_FALSE(storage.SetEntryValue(0u, {}));
}
class LocalStorageDuplicatesTest : public LocalStorageTest {
public:
void SetupPubSub(bool keepPub, bool keepSub);
void SetValues();
NT_Publisher pub;
NT_Subscriber sub;
Value val1 = Value::MakeDouble(1.0, 10);
Value val2 = Value::MakeDouble(1.0, 20); // duplicate value
Value val3 = Value::MakeDouble(2.0, 30);
};
void LocalStorageDuplicatesTest::SetupPubSub(bool keepPub, bool keepSub) {
PubSubOptions pubOptions;
pubOptions.keepDuplicates = keepPub;
EXPECT_CALL(network, Publish(_, fooTopic, std::string_view{"foo"},
std::string_view{"double"}, wpi::json::object(),
IsPubSubOptions(pubOptions)));
pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {},
{{PubSubOption::KeepDuplicates(keepPub)}});
PubSubOptions subOptions;
subOptions.pollStorageSize = 10;
subOptions.keepDuplicates = keepSub;
EXPECT_CALL(network, Subscribe(_, wpi::SpanEq({std::string{"foo"}}),
IsPubSubOptions(subOptions)));
sub = storage.Subscribe(
fooTopic, NT_DOUBLE, "double",
{{PubSubOption::KeepDuplicates(keepSub), PubSubOption::PollStorage(10)}});
}
void LocalStorageDuplicatesTest::SetValues() {
storage.SetEntryValue(pub, val1);
storage.SetEntryValue(pub, val2);
// verify the timestamp was updated
EXPECT_EQ(storage.GetEntryLastChange(sub), val2.time());
storage.SetEntryValue(pub, val3);
}
TEST_F(LocalStorageDuplicatesTest, Defaults) {
SetupPubSub(false, false);
EXPECT_CALL(network, SetValue(pub, val1));
EXPECT_CALL(network, SetValue(pub, val3));
SetValues();
// verify 2nd update was dropped locally
auto values = storage.ReadQueueDouble(sub);
ASSERT_EQ(values.size(), 2u);
ASSERT_EQ(values[0].value, val1.GetDouble());
ASSERT_EQ(values[0].time, val1.time());
ASSERT_EQ(values[1].value, val3.GetDouble());
ASSERT_EQ(values[1].time, val3.time());
}
TEST_F(LocalStorageDuplicatesTest, KeepPub) {
SetupPubSub(true, false);
EXPECT_CALL(network, SetValue(pub, val1)).Times(2);
// EXPECT_CALL(network, SetValue(pub, val2));
EXPECT_CALL(network, SetValue(pub, val3));
SetValues();
// verify all 3 updates were received locally
auto values = storage.ReadQueueDouble(sub);
ASSERT_EQ(values.size(), 3u);
}
TEST_F(LocalStorageDuplicatesTest, KeepSub) {
SetupPubSub(false, true);
// second update should NOT go to the network
EXPECT_CALL(network, SetValue(pub, val1));
EXPECT_CALL(network, SetValue(pub, val3));
SetValues();
// verify all 3 updates were received locally
auto values = storage.ReadQueueDouble(sub);
ASSERT_EQ(values.size(), 3u);
}
TEST_F(LocalStorageDuplicatesTest, FromNetwork) {
SetupPubSub(false, false);
// incoming from the network are treated like a normal local publish
auto topic = storage.NetworkAnnounce("foo", "double", {{}}, 0);
storage.NetworkSetValue(topic, val1);
storage.NetworkSetValue(topic, val2);
// verify the timestamp was updated
EXPECT_EQ(storage.GetEntryLastChange(sub), val2.time());
storage.NetworkSetValue(topic, val3);
// verify 2nd update was dropped locally
auto values = storage.ReadQueueDouble(sub);
ASSERT_EQ(values.size(), 2u);
ASSERT_EQ(values[0].value, val1.GetDouble());
ASSERT_EQ(values[0].time, val1.time());
ASSERT_EQ(values[1].value, val3.GetDouble());
ASSERT_EQ(values[1].time, val3.time());
}
class LocalStorageNumberVariantsTest : public LocalStorageTest {
public:
void CreateSubscriber(NT_Handle* handle, std::string_view name, NT_Type type,
std::string_view typeStr);
void CreateSubscribers();
void CreateSubscribersArray();
NT_Subscriber sub1, sub2, sub3, sub4;
NT_Entry entry;
struct SubEntry {
SubEntry(NT_Handle subentry, NT_Type type, std::string_view name)
: subentry{subentry}, type{type}, name{name} {}
NT_Handle subentry;
NT_Type type;
std::string name;
};
std::vector<SubEntry> subentries;
};
void LocalStorageNumberVariantsTest::CreateSubscriber(
NT_Handle* handle, std::string_view name, NT_Type type,
std::string_view typeStr) {
*handle = storage.Subscribe(fooTopic, type, typeStr, {});
subentries.emplace_back(*handle, type, name);
}
void LocalStorageNumberVariantsTest::CreateSubscribers() {
EXPECT_CALL(logger,
Call(NT_LOG_INFO, _, _,
"local subscribe to 'foo' disabled due to type "
"mismatch (wanted 'boolean', published as 'double')"));
CreateSubscriber(&sub1, "subDouble", NT_DOUBLE, "double");
CreateSubscriber(&sub2, "subInteger", NT_INTEGER, "int");
CreateSubscriber(&sub3, "subFloat", NT_FLOAT, "float");
CreateSubscriber(&sub4, "subBoolean", NT_BOOLEAN, "boolean");
entry = storage.GetEntry("foo");
subentries.emplace_back(entry, NT_UNASSIGNED, "entry");
}
void LocalStorageNumberVariantsTest::CreateSubscribersArray() {
EXPECT_CALL(logger,
Call(NT_LOG_INFO, _, _,
"local subscribe to 'foo' disabled due to type "
"mismatch (wanted 'boolean[]', published as 'double[]')"));
CreateSubscriber(&sub1, "subDouble", NT_DOUBLE_ARRAY, "double[]");
CreateSubscriber(&sub2, "subInteger", NT_INTEGER_ARRAY, "int[]");
CreateSubscriber(&sub3, "subFloat", NT_FLOAT_ARRAY, "float[]");
CreateSubscriber(&sub4, "subBoolean", NT_BOOLEAN_ARRAY, "boolean[]");
entry = storage.GetEntry("foo");
subentries.emplace_back(entry, NT_UNASSIGNED, "entry");
}
TEST_F(LocalStorageNumberVariantsTest, GetEntryPubAfter) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
EXPECT_CALL(network, SetValue(_, _)).Times(1);
CreateSubscribers();
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
// all subscribers get the actual type and time
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
EXPECT_EQ(storage.GetEntryType(subentry.subentry), NT_DOUBLE);
EXPECT_EQ(storage.GetEntryLastChange(subentry.subentry), 50);
}
// for subscribers, they get a converted value or nothing on mismatch
EXPECT_EQ(storage.GetEntryValue(sub1), Value::MakeDouble(1.0, 50));
EXPECT_EQ(storage.GetEntryValue(sub2), Value::MakeInteger(1, 50));
EXPECT_EQ(storage.GetEntryValue(sub3), Value::MakeFloat(1.0, 50));
EXPECT_EQ(storage.GetEntryValue(sub4), Value{});
// entrys just get whatever the value is
EXPECT_EQ(storage.GetEntryValue(entry), Value::MakeDouble(1.0, 50));
}
TEST_F(LocalStorageNumberVariantsTest, GetEntryPubBefore) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
EXPECT_CALL(network, SetValue(_, _)).Times(1);
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
CreateSubscribers();
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
// all subscribers get the actual type and time
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
EXPECT_EQ(storage.GetEntryType(subentry.subentry), NT_DOUBLE);
EXPECT_EQ(storage.GetEntryLastChange(subentry.subentry), 50);
}
// for subscribers, they get a converted value or nothing on mismatch
EXPECT_EQ(storage.GetEntryValue(sub1), Value::MakeDouble(1.0, 50));
EXPECT_EQ(storage.GetEntryValue(sub2), Value::MakeInteger(1, 50));
EXPECT_EQ(storage.GetEntryValue(sub3), Value::MakeFloat(1.0, 50));
EXPECT_EQ(storage.GetEntryValue(sub4), Value{});
// entrys just get whatever the value is
EXPECT_EQ(storage.GetEntryValue(entry), Value::MakeDouble(1.0, 50));
}
template <typename T>
::testing::Matcher<const T&> TSEq(auto value, int64_t time) {
return AllOf(Field("value", &T::value, value), Field("time", &T::time, time));
}
TEST_F(LocalStorageNumberVariantsTest, GetAtomic) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
EXPECT_CALL(network, SetValue(_, _)).Times(1);
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
CreateSubscribers();
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
EXPECT_THAT(storage.GetAtomicDouble(subentry.subentry, 0),
TSEq<TimestampedDouble>(1.0, 50));
EXPECT_THAT(storage.GetAtomicInteger(subentry.subentry, 0),
TSEq<TimestampedInteger>(1, 50));
EXPECT_THAT(storage.GetAtomicFloat(subentry.subentry, 0),
TSEq<TimestampedFloat>(1.0, 50));
EXPECT_THAT(storage.GetAtomicBoolean(subentry.subentry, false),
TSEq<TimestampedBoolean>(false, 0));
}
}
template <typename T, typename U>
::testing::Matcher<const T&> TSSpanEq(std::span<U> value, int64_t time) {
return AllOf(
Field("value", &T::value, wpi::SpanEq(std::span<const U>(value))),
Field("time", &T::time, time));
}
TEST_F(LocalStorageNumberVariantsTest, GetAtomicArray) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
EXPECT_CALL(network, SetValue(_, _)).Times(1);
auto pub = storage.Publish(fooTopic, NT_DOUBLE_ARRAY, "double[]", {}, {});
CreateSubscribersArray();
storage.SetEntryValue(pub, Value::MakeDoubleArray({1.0}, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
double doubleVal = 1.0;
EXPECT_THAT(storage.GetAtomicDoubleArray(subentry.subentry, {}),
TSSpanEq<TimestampedDoubleArray>(std::span{&doubleVal, 1}, 50));
int64_t intVal = 1;
EXPECT_THAT(storage.GetAtomicIntegerArray(subentry.subentry, {}),
TSSpanEq<TimestampedIntegerArray>(std::span{&intVal, 1}, 50));
float floatVal = 1.0;
EXPECT_THAT(storage.GetAtomicFloatArray(subentry.subentry, {}),
TSSpanEq<TimestampedFloatArray>(std::span{&floatVal, 1}, 50));
EXPECT_THAT(storage.GetAtomicBooleanArray(subentry.subentry, {}),
TSSpanEq<TimestampedBooleanArray>(std::span<int>{}, 0));
}
}
TEST_F(LocalStorageNumberVariantsTest, ReadQueue) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
EXPECT_CALL(network, SetValue(_, _)).Times(4);
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
CreateSubscribers();
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
if (subentry.type == NT_BOOLEAN) {
EXPECT_THAT(storage.ReadQueueDouble(subentry.subentry), IsEmpty());
} else {
EXPECT_THAT(storage.ReadQueueDouble(subentry.subentry),
ElementsAre(TSEq<TimestampedDouble>(1.0, 50)));
}
}
storage.SetEntryValue(pub, Value::MakeDouble(2.0, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
if (subentry.type == NT_BOOLEAN) {
EXPECT_THAT(storage.ReadQueueInteger(subentry.subentry), IsEmpty());
} else {
EXPECT_THAT(storage.ReadQueueInteger(subentry.subentry),
ElementsAre(TSEq<TimestampedInteger>(2, 50)));
}
}
storage.SetEntryValue(pub, Value::MakeDouble(3.0, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
if (subentry.type == NT_BOOLEAN) {
EXPECT_THAT(storage.ReadQueueFloat(subentry.subentry), IsEmpty());
} else {
EXPECT_THAT(storage.ReadQueueFloat(subentry.subentry),
ElementsAre(TSEq<TimestampedFloat>(3.0, 50)));
}
}
storage.SetEntryValue(pub, Value::MakeDouble(4.0, 50));
for (auto&& subentry : subentries) {
SCOPED_TRACE(subentry.name);
EXPECT_THAT(storage.ReadQueueBoolean(subentry.subentry), IsEmpty());
}
}
} // namespace nt

View File

@@ -89,3 +89,15 @@ TEST_F(NetworkTableTest, EmptyOrNoSlash) {
ASSERT_TRUE(inst.GetEntry("/testkey").Exists());
nt::NetworkTableInstance::Destroy(inst);
}
TEST_F(NetworkTableTest, ResetInstance) {
auto inst = nt::NetworkTableInstance::Create();
auto nt = inst.GetTable("containskey");
ASSERT_FALSE(nt->ContainsKey("testkey"));
nt->PutNumber("testkey", 5);
ASSERT_TRUE(nt->ContainsKey("testkey"));
ASSERT_TRUE(inst.GetEntry("/containskey/testkey").Exists());
nt::ResetInstance(inst.GetHandle());
ASSERT_FALSE(nt->ContainsKey("testkey"));
nt::NetworkTableInstance::Destroy(inst);
}

View File

@@ -121,37 +121,37 @@ void PrintTo(const Value& value, std::ostream* os) {
case NT_UNASSIGNED:
break;
case NT_BOOLEAN:
*os << (value.GetBoolean() ? "true" : "false");
*os << "boolean, " << (value.GetBoolean() ? "true" : "false");
break;
case NT_DOUBLE:
*os << value.GetDouble();
*os << "double, " << value.GetDouble();
break;
case NT_FLOAT:
*os << value.GetFloat();
*os << "float, " << value.GetFloat();
break;
case NT_INTEGER:
*os << value.GetInteger();
*os << "int, " << value.GetInteger();
break;
case NT_STRING:
*os << '"' << value.GetString() << '"';
*os << "string, \"" << value.GetString() << '"';
break;
case NT_RAW:
*os << ::testing::PrintToString(value.GetRaw());
*os << "raw, " << ::testing::PrintToString(value.GetRaw());
break;
case NT_BOOLEAN_ARRAY:
*os << ::testing::PrintToString(value.GetBooleanArray());
*os << "boolean[], " << ::testing::PrintToString(value.GetBooleanArray());
break;
case NT_DOUBLE_ARRAY:
*os << ::testing::PrintToString(value.GetDoubleArray());
*os << "double[], " << ::testing::PrintToString(value.GetDoubleArray());
break;
case NT_FLOAT_ARRAY:
*os << ::testing::PrintToString(value.GetFloatArray());
*os << "float[], " << ::testing::PrintToString(value.GetFloatArray());
break;
case NT_INTEGER_ARRAY:
*os << ::testing::PrintToString(value.GetIntegerArray());
*os << "int[], " << ::testing::PrintToString(value.GetIntegerArray());
break;
case NT_STRING_ARRAY:
*os << ::testing::PrintToString(value.GetStringArray());
*os << "string[], " << ::testing::PrintToString(value.GetStringArray());
break;
default:
*os << "UNKNOWN TYPE " << value.type();

View File

@@ -346,4 +346,64 @@ TEST_F(ValueListenerTest, PollImmediateSubMultiple) {
EXPECT_EQ(valueData->value, nt::Value::MakeDouble(1.0));
}
TEST_F(ValueListenerTest, TwoSubOneListener) {
auto topic = nt::GetTopic(m_inst, "foo");
auto pub = nt::Publish(topic, NT_DOUBLE, "double");
auto sub1 = nt::Subscribe(topic, NT_DOUBLE, "double");
auto sub2 = nt::Subscribe(topic, NT_DOUBLE, "double");
auto sub3 = nt::SubscribeMultiple(m_inst, {{"foo"}});
auto poller = nt::CreateListenerPoller(m_inst);
auto h = nt::AddPolledListener(poller, sub1, nt::EventFlags::kValueLocal);
(void)sub2;
(void)sub3;
nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
auto results = nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags & nt::EventFlags::kValueLocal,
nt::EventFlags::kValueLocal);
EXPECT_EQ(results[0].listener, h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub1);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0));
}
TEST_F(ValueListenerTest, TwoSubOneMultiListener) {
auto topic = nt::GetTopic(m_inst, "foo");
auto pub = nt::Publish(topic, NT_DOUBLE, "double");
auto sub1 = nt::Subscribe(topic, NT_DOUBLE, "double");
auto sub2 = nt::Subscribe(topic, NT_DOUBLE, "double");
auto sub3 = nt::SubscribeMultiple(m_inst, {{"foo"}});
auto poller = nt::CreateListenerPoller(m_inst);
auto h = nt::AddPolledListener(poller, sub3, nt::EventFlags::kValueLocal);
(void)sub1;
(void)sub2;
nt::SetDouble(pub, 0);
bool timedOut = false;
ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut));
ASSERT_FALSE(timedOut);
auto results = nt::ReadListenerQueue(poller);
ASSERT_EQ(results.size(), 1u);
EXPECT_EQ(results[0].flags & nt::EventFlags::kValueLocal,
nt::EventFlags::kValueLocal);
EXPECT_EQ(results[0].listener, h);
auto valueData = results[0].GetValueEventData();
ASSERT_TRUE(valueData);
EXPECT_EQ(valueData->subentry, sub3);
EXPECT_EQ(valueData->topic, topic);
EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0));
}
} // namespace nt

View File

@@ -37,6 +37,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

View File

@@ -37,6 +37,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

View File

@@ -52,6 +52,7 @@ include 'myRobot'
include 'docs'
include 'msvcruntime'
include 'ntcoreffi'
include 'apriltag'
buildCache {
def cred = {

View File

@@ -15,9 +15,9 @@ nativeUtils {
configureDependencies {
wpiVersion = "-1"
niLibVersion = "2023.1.0"
opencvVersion = "4.6.0-2"
googleTestVersion = "1.11.0-3"
imguiVersion = "1.88-8"
opencvVersion = "4.6.0-3"
googleTestVersion = "1.11.0-4"
imguiVersion = "1.88-9"
wpimathVersion = "-1"
}
}
@@ -34,14 +34,6 @@ nativeUtils.setSinglePrintPerPlatform()
nativeUtils.enableSourceLink()
nativeUtils.platformConfigs.each {
if (it.name.contains('windows')) {
it.cppCompiler.args.remove("/std:c++17")
it.cppCompiler.args.add("/std:c++20")
return
} else if (it.name == 'linuxx86-64' || it.name == 'osxx86-64' || it.name == 'osxarm64') {
it.cppCompiler.args.remove("-std=c++17")
it.cppCompiler.args.add("-std=c++20")
}
if (it.name.contains('osx')) {
it.linker.args << '-Wl,-rpath,\'@loader_path\''
it.linker.args << "-headerpad_max_install_names"

View File

@@ -104,6 +104,10 @@ model {
baseName = nativeName + 'jni'
}
if (project.hasProperty('skipJniSymbols')) {
checkSkipSymbols = skipJniSymbols
}
enableCheckTask !project.hasProperty('skipJniCheck')
javaCompileTasks << compileJava
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)
@@ -148,6 +152,10 @@ model {
baseName = nativeName + 'jni'
}
if (project.hasProperty('skipJniSymbols')) {
checkSkipSymbols = skipJniSymbols
}
enableCheckTask !project.hasProperty('skipJniCheck')
javaCompileTasks << compileJava
jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio)

View File

@@ -6,7 +6,7 @@ nativeUtils {
headerClassifier = "headers"
sourceClassifier = "sources"
ext = "zip"
version = '0.95-5'
version = '0.95-6'
targetPlatforms.addAll(nativeUtils.wpi.platforms.allPlatforms)
}
}

View File

@@ -0,0 +1,22 @@
From 05864e768ca1458c1e24f433d091306a7d47562b Mon Sep 17 00:00:00 2001
From: PJ Reiniger <pj.reiniger@gmail.com>
Date: Sat, 29 Oct 2022 12:09:03 -0400
Subject: [PATCH 1/3] Don't emit inline defs
---
src/mpack/mpack-platform.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mpack/mpack-platform.c b/src/mpack/mpack-platform.c
index 6599e1f..d4a2fa3 100644
--- a/src/mpack/mpack-platform.c
+++ b/src/mpack/mpack-platform.c
@@ -24,7 +24,7 @@
// standalone definitions of all (non-static) inline functions in MPack.
#define MPACK_INTERNAL 1
-#define MPACK_EMIT_INLINE_DEFS 1
+#define MPACK_EMIT_INLINE_DEFS 0
#include "mpack-platform.h"
#include "mpack.h"

View File

@@ -0,0 +1,24 @@
From d4d045c843d4b4de747d800e570c32cff3759a80 Mon Sep 17 00:00:00 2001
From: PJ Reiniger <pj.reiniger@gmail.com>
Date: Sat, 29 Oct 2022 12:16:36 -0400
Subject: [PATCH 2/3] Update amalgamation script
---
tools/amalgamate.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/amalgamate.sh b/tools/amalgamate.sh
index 2e24e27..4dfe999 100755
--- a/tools/amalgamate.sh
+++ b/tools/amalgamate.sh
@@ -74,8 +74,8 @@ echo -e "#endif\n" >> $HEADER
# assemble source
echo -e "#define MPACK_INTERNAL 1" >> $SOURCE
-echo -e "#define MPACK_EMIT_INLINE_DEFS 1\n" >> $SOURCE
-echo -e "#include \"mpack.h\"\n" >> $SOURCE
+echo -e "#define MPACK_EMIT_INLINE_DEFS 0\n" >> $SOURCE
+echo -e "#include \"wpi/mpack.h\"\n" >> $SOURCE
for f in $SOURCES; do
echo -e "\n/* $f.c */" >> $SOURCE
sed -e 's@^#include ".*@/* & */@' -e '0,/^ \*\/$/d' src/$f >> $SOURCE

View File

@@ -0,0 +1,158 @@
From 37854ea8a4a4b387940719c40bd32792f1e6e027 Mon Sep 17 00:00:00 2001
From: PJ Reiniger <pj.reiniger@gmail.com>
Date: Sat, 29 Oct 2022 12:22:50 -0400
Subject: [PATCH 3/3] Use namespace for C++
---
src/mpack/mpack-common.c | 2 ++
src/mpack/mpack-expect.c | 2 ++
src/mpack/mpack-node.c | 2 ++
src/mpack/mpack-platform.c | 2 ++
src/mpack/mpack-platform.h | 2 +-
src/mpack/mpack-reader.c | 2 ++
src/mpack/mpack-writer.c | 2 ++
src/mpack/mpack-writer.h | 3 ++-
8 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/mpack/mpack-common.c b/src/mpack/mpack-common.c
index 2c133a3..dc7207f 100644
--- a/src/mpack/mpack-common.c
+++ b/src/mpack/mpack-common.c
@@ -24,6 +24,7 @@
#include "mpack-common.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
const char* mpack_error_to_string(mpack_error_t error) {
#if MPACK_STRINGS
@@ -748,4 +749,5 @@ void mpack_print_file_callback(void* context, const char* data, size_t count) {
}
#endif
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-expect.c b/src/mpack/mpack-expect.c
index 81576d1..6232a67 100644
--- a/src/mpack/mpack-expect.c
+++ b/src/mpack/mpack-expect.c
@@ -24,6 +24,7 @@
#include "mpack-expect.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
#if MPACK_EXPECT
@@ -880,4 +881,5 @@ size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], bool fo
#endif
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-node.c b/src/mpack/mpack-node.c
index 3d4b0f4..aba9897 100644
--- a/src/mpack/mpack-node.c
+++ b/src/mpack/mpack-node.c
@@ -24,6 +24,7 @@
#include "mpack-node.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
#if MPACK_NODE
@@ -2401,4 +2402,5 @@ mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) {
#endif
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-platform.c b/src/mpack/mpack-platform.c
index d4a2fa3..75d2de3 100644
--- a/src/mpack/mpack-platform.c
+++ b/src/mpack/mpack-platform.c
@@ -30,6 +30,7 @@
#include "mpack.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
#if MPACK_DEBUG
@@ -218,4 +219,5 @@ void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
}
#endif
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-platform.h b/src/mpack/mpack-platform.h
index 79604c9..27a2f9e 100644
--- a/src/mpack/mpack-platform.h
+++ b/src/mpack/mpack-platform.h
@@ -1043,7 +1043,7 @@ void mpack_assert_fail(const char* message);
*/
#ifdef __cplusplus
- #define MPACK_EXTERN_C_BEGIN extern "C" {
+ #define MPACK_EXTERN_C_BEGIN namespace mpack {
#define MPACK_EXTERN_C_END }
#else
#define MPACK_EXTERN_C_BEGIN /*nothing*/
diff --git a/src/mpack/mpack-reader.c b/src/mpack/mpack-reader.c
index c6d2223..a135879 100644
--- a/src/mpack/mpack-reader.c
+++ b/src/mpack/mpack-reader.c
@@ -24,6 +24,7 @@
#include "mpack-reader.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
#if MPACK_READER
@@ -1284,4 +1285,5 @@ void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback
#endif
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-writer.c b/src/mpack/mpack-writer.c
index 4d052b1..9630d9e 100644
--- a/src/mpack/mpack-writer.c
+++ b/src/mpack/mpack-writer.c
@@ -24,6 +24,7 @@
#include "mpack-writer.h"
MPACK_SILENCE_WARNINGS_BEGIN
+namespace mpack {
#if MPACK_WRITER
@@ -1772,4 +1773,5 @@ void mpack_complete_array(mpack_writer_t* writer) {
#endif // MPACK_BUILDER
#endif // MPACK_WRITER
+} // namespace mpack
MPACK_SILENCE_WARNINGS_END
diff --git a/src/mpack/mpack-writer.h b/src/mpack/mpack-writer.h
index c239ee6..abeee1a 100644
--- a/src/mpack/mpack-writer.h
+++ b/src/mpack/mpack-writer.h
@@ -1168,6 +1168,7 @@ MPACK_EXTERN_C_END
#if defined(__cplusplus) || defined(MPACK_DOXYGEN)
+namespace mpack {
/**
* @name C++ write overloads
* @{
@@ -1304,7 +1305,7 @@ MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, const
/**
* @}
*/
-
+} // namespace mpack
#endif /* __cplusplus */
/**

View File

@@ -73,12 +73,8 @@ def eigen_inclusions(dp, f):
]
modules_rgx = r"|".join("/" + m for m in modules)
# "Std" matches StdDeque, StdList, and StdVector headers
if re.search(modules_rgx, abspath) or "Std" in f:
return True
else:
# Exclude all other modules
return False
# "Std" matches StdDeque, StdList, and StdVector headers. Other modules are excluded.
return bool(re.search(modules_rgx, abspath) or "Std" in f)
def unsupported_inclusions(dp, f):

View File

@@ -126,12 +126,12 @@ def overwrite_files(wpiutil_files, llvm_files):
for wpi_file in wpiutil_files:
wpi_base_name = os.path.basename(wpi_file)
if wpi_base_name not in llvm_files:
if wpi_base_name not in unmatched_files_whitelist:
print(f"No file match for {wpi_file}, check if LLVM deleted it")
else:
if wpi_base_name in llvm_files:
shutil.copyfile(llvm_files[wpi_base_name], wpi_file)
elif wpi_base_name not in unmatched_files_whitelist:
print(f"No file match for {wpi_file}, check if LLVM deleted it")
def overwrite_source(wpiutil_root, llvm_root):
llvm_files = flattened_llvm_files(

59
upstream_utils/update_mpack.py Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import os
import shutil
import subprocess
from upstream_utils import (
get_repo_root,
clone_repo,
walk_cwd_and_copy_if,
git_am,
)
def main():
upstream_root = clone_repo("https://github.com/ludocode/mpack", "v1.1")
wpilib_root = get_repo_root()
wpiutil = os.path.join(wpilib_root, "wpiutil")
# Delete old install
for d in [
"src/main/native/thirdparty/mpack/src",
"src/main/native/thirdparty/mpack/include",
]:
shutil.rmtree(os.path.join(wpiutil, d), ignore_errors=True)
# Apply patches to upstream Git repo
os.chdir(upstream_root)
for f in [
"0001-Don-t-emit-inline-defs.patch",
"0002-Update-amalgamation-script.patch",
"0003-Use-namespace-for-C.patch",
]:
git_am(
os.path.join(wpilib_root, "upstream_utils/mpack_patches", f),
)
# Run the amalgmation script
subprocess.check_call(["bash", "tools/amalgamate.sh"])
# Copy the files
amalgamation_source_dir = os.path.join(
".", ".build", "amalgamation", "src", "mpack"
)
os.chdir(amalgamation_source_dir)
walk_cwd_and_copy_if(
lambda dp, f: f.endswith(".h"),
os.path.join(wpiutil, "src/main/native/thirdparty/mpack/include/wpi"),
)
walk_cwd_and_copy_if(
lambda dp, f: f.endswith(".c"),
os.path.join(wpiutil, "src/main/native/thirdparty/mpack/src"),
)
if __name__ == "__main__":
main()

View File

@@ -5,6 +5,7 @@ find_dependency(Threads)
@LIBUV_SYSTEM_REPLACE@
@EIGEN_SYSTEM_REPLACE@
@WPIUTIL_DEP_REPLACE@
@WPINET_DEP_REPLACE@
@NTCORE_DEP_REPLACE@
@CSCORE_DEP_REPLACE@
@CAMERASERVER_DEP_REPLACE@

View File

@@ -30,8 +30,7 @@
"windowsx86-64",
"windowsx86",
"linuxx86-64",
"osxx86-64",
"osxarm64"
"osxuniversal"
]
}
]

View File

@@ -56,6 +56,12 @@ public abstract class CommandBase implements Sendable, Command {
SendableRegistry.setName(this, name);
}
@Override
public CommandBase withName(String name) {
this.setName(name);
return this;
}
/**
* Gets the subsystem name of this Command.
*

View File

@@ -405,10 +405,6 @@ public final class CommandScheduler implements NTSendable, AutoCloseable {
throw new IllegalArgumentException("Default commands must require their subsystem!");
}
if (defaultCommand.isFinished()) {
throw new IllegalArgumentException("Default commands should not end!");
}
if (defaultCommand.getInterruptionBehavior() == InterruptionBehavior.kCancelIncoming) {
DriverStation.reportWarning(
"Registering a non-interruptible default command!\n"

View File

@@ -21,7 +21,7 @@ public final class Commands {
*
* @return the command
*/
public static Command none() {
public static CommandBase none() {
return new InstantCommand();
}
@@ -35,7 +35,7 @@ public final class Commands {
* @return the command
* @see InstantCommand
*/
public static Command runOnce(Runnable action, Subsystem... requirements) {
public static CommandBase runOnce(Runnable action, Subsystem... requirements) {
return new InstantCommand(action, requirements);
}
@@ -47,7 +47,7 @@ public final class Commands {
* @return the command
* @see RunCommand
*/
public static Command run(Runnable action, Subsystem... requirements) {
public static CommandBase run(Runnable action, Subsystem... requirements) {
return new RunCommand(action, requirements);
}
@@ -61,7 +61,7 @@ public final class Commands {
* @return the command
* @see StartEndCommand
*/
public static Command startEnd(Runnable start, Runnable end, Subsystem... requirements) {
public static CommandBase startEnd(Runnable start, Runnable end, Subsystem... requirements) {
return new StartEndCommand(start, end, requirements);
}
@@ -74,7 +74,7 @@ public final class Commands {
* @param requirements subsystems the action requires
* @return the command
*/
public static Command runEnd(Runnable run, Runnable end, Subsystem... requirements) {
public static CommandBase runEnd(Runnable run, Runnable end, Subsystem... requirements) {
requireNonNullParam(end, "end", "Command.runEnd");
return new FunctionalCommand(
() -> {}, run, interrupted -> end.run(), () -> false, requirements);
@@ -87,7 +87,7 @@ public final class Commands {
* @return the command
* @see PrintCommand
*/
public static Command print(String message) {
public static CommandBase print(String message) {
return new PrintCommand(message);
}
@@ -100,7 +100,7 @@ public final class Commands {
* @return the command
* @see WaitCommand
*/
public static Command wait(double seconds) {
public static CommandBase wait(double seconds) {
return new WaitCommand(seconds);
}
@@ -111,7 +111,7 @@ public final class Commands {
* @return the command
* @see WaitUntilCommand
*/
public static Command waitUntil(BooleanSupplier condition) {
public static CommandBase waitUntil(BooleanSupplier condition) {
return new WaitUntilCommand(condition);
}
@@ -126,7 +126,7 @@ public final class Commands {
* @return the command
* @see ConditionalCommand
*/
public static Command either(Command onTrue, Command onFalse, BooleanSupplier selector) {
public static CommandBase either(Command onTrue, Command onFalse, BooleanSupplier selector) {
return new ConditionalCommand(onTrue, onFalse, selector);
}
@@ -138,7 +138,7 @@ public final class Commands {
* @return the command
* @see SelectCommand
*/
public static Command select(Map<Object, Command> commands, Supplier<Object> selector) {
public static CommandBase select(Map<Object, Command> commands, Supplier<Object> selector) {
return new SelectCommand(commands, selector);
}
@@ -149,7 +149,7 @@ public final class Commands {
* @return the command group
* @see SequentialCommandGroup
*/
public static Command sequence(Command... commands) {
public static CommandBase sequence(Command... commands) {
return new SequentialCommandGroup(commands);
}
@@ -164,7 +164,7 @@ public final class Commands {
* @see SequentialCommandGroup
* @see Command#repeatedly()
*/
public static Command repeatingSequence(Command... commands) {
public static CommandBase repeatingSequence(Command... commands) {
return sequence(commands).repeatedly();
}
@@ -175,7 +175,7 @@ public final class Commands {
* @return the command
* @see ParallelCommandGroup
*/
public static Command parallel(Command... commands) {
public static CommandBase parallel(Command... commands) {
return new ParallelCommandGroup(commands);
}
@@ -187,7 +187,7 @@ public final class Commands {
* @return the command group
* @see ParallelRaceGroup
*/
public static Command race(Command... commands) {
public static CommandBase race(Command... commands) {
return new ParallelRaceGroup(commands);
}
@@ -200,7 +200,7 @@ public final class Commands {
* @return the command group
* @see ParallelDeadlineGroup
*/
public static Command deadline(Command deadline, Command... commands) {
public static CommandBase deadline(Command deadline, Command... commands) {
return new ParallelDeadlineGroup(deadline, commands);
}

View File

@@ -16,7 +16,7 @@ import java.util.Set;
* <p>Wrapped commands may only be used through the wrapper, trying to directly schedule them or add
* them to a group will throw an exception.
*/
public abstract class WrapperCommand implements Command {
public abstract class WrapperCommand extends CommandBase {
protected final Command m_command;
/**
@@ -99,14 +99,4 @@ public abstract class WrapperCommand implements Command {
public InterruptionBehavior getInterruptionBehavior() {
return m_command.getInterruptionBehavior();
}
/**
* Gets the name of this Command.
*
* @return Name
*/
@Override
public String getName() {
return m_command.getName();
}
}

View File

@@ -4,7 +4,6 @@
package edu.wpi.first.wpilibj2.command.button;
import edu.wpi.first.wpilibj.GenericHID;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.event.EventLoop;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
@@ -33,7 +32,7 @@ public class CommandJoystick extends CommandGenericHID {
* @return the wrapped GenericHID object
*/
@Override
public GenericHID getHID() {
public Joystick getHID() {
return m_hid;
}
@@ -171,6 +170,24 @@ public class CommandJoystick extends CommandGenericHID {
return m_hid.getThrottleChannel();
}
/**
* Get the x position of the HID.
*
* @return the x position
*/
public double getX() {
return m_hid.getX();
}
/**
* Get the y position of the HID.
*
* @return the y position
*/
public double getY() {
return m_hid.getY();
}
/**
* Get the z position of the HID.
*

View File

@@ -4,7 +4,6 @@
package edu.wpi.first.wpilibj2.command.button;
import edu.wpi.first.wpilibj.GenericHID;
import edu.wpi.first.wpilibj.PS4Controller;
import edu.wpi.first.wpilibj.event.EventLoop;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
@@ -34,7 +33,7 @@ public class CommandPS4Controller extends CommandGenericHID {
* @return the wrapped GenericHID object
*/
@Override
public GenericHID getHID() {
public PS4Controller getHID() {
return m_hid;
}
@@ -206,6 +205,27 @@ public class CommandPS4Controller extends CommandGenericHID {
return m_hid.cross(loop).castTo(Trigger::new);
}
/**
* Constructs an event instance around the triangle button's digital signal.
*
* @return an event instance representing the triangle button's digital signal attached to the
* {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
*/
public Trigger triangle() {
return triangle(CommandScheduler.getInstance().getDefaultButtonLoop());
}
/**
* Constructs an event instance around the triangle button's digital signal.
*
* @param loop the event loop instance to attach the event to.
* @return an event instance representing the triangle button's digital signal attached to the
* given loop.
*/
public Trigger triangle(EventLoop loop) {
return m_hid.triangle(loop).castTo(Trigger::new);
}
/**
* Constructs an event instance around the circle button's digital signal.
*

View File

@@ -17,8 +17,13 @@ import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
/**
* This class is a wrapper around {@link BooleanEvent}, providing an easy way to link commands to
* digital inputs.
* This class provides an easy way to link commands to conditions.
*
* <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>This class is provided by the NewCommands VendorDep
*/
@@ -26,13 +31,13 @@ public class Trigger implements BooleanSupplier {
private final BooleanEvent m_event;
/**
* Creates a new trigger with the given condition/digital signal.
* Creates a new trigger based on the given condition.
*
* @param loop the loop that polls this trigger
* @param signal the digital signal represented.
* @param loop The loop instance that polls this trigger.
* @param condition the condition represented by this trigger
*/
public Trigger(EventLoop loop, BooleanSupplier signal) {
m_event = new BooleanEvent(loop, signal);
public Trigger(EventLoop loop, BooleanSupplier condition) {
m_event = new BooleanEvent(loop, condition);
}
/**
@@ -47,24 +52,24 @@ public class Trigger implements BooleanSupplier {
}
/**
* Creates a new trigger with the given condition/digital signal.
* Creates a new trigger based on the given condition.
*
* <p>Polled by the {@link CommandScheduler#getDefaultButtonLoop() default scheduler button loop}.
* <p>Polled by the default scheduler button loop.
*
* @param signal the digital signal represented.
* @param condition the condition represented by this trigger
*/
public Trigger(BooleanSupplier signal) {
this(CommandScheduler.getInstance().getDefaultButtonLoop(), signal);
public Trigger(BooleanSupplier condition) {
this(CommandScheduler.getInstance().getDefaultButtonLoop(), condition);
}
/** Creates a new trigger that is always inactive. */
/** Creates a new trigger that is always `false`. */
@Deprecated
public Trigger() {
this(() -> false);
}
/**
* Starts the given command whenever the signal rises from the low state to the high state.
* 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
@@ -77,7 +82,7 @@ public class Trigger implements BooleanSupplier {
}
/**
* Starts the given command whenever the signal falls from the high state to the low state.
* 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
@@ -90,10 +95,11 @@ public class Trigger implements BooleanSupplier {
}
/**
* Starts the given command when the signal rises to the high state and cancels it when the signal
* falls.
* 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 in-between.
* <p>Doesn't re-start the command if it ends while the condition is still `true`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
*
* @param command the command to start
* @return this trigger, so calls can be chained
@@ -106,10 +112,11 @@ public class Trigger implements BooleanSupplier {
}
/**
* Starts the given command when the signal falls to the low state and cancels it when the signal
* rises.
* Starts the given command when the condition changes to `false` and cancels it when the
* condition changes to `true`.
*
* <p>Does not re-start the command in-between.
* <p>Doesn't re-start the command if it ends while the condition is still `false`. If the command
* should restart, see {@link edu.wpi.first.wpilibj2.command.RepeatCommand}.
*
* @param command the command to start
* @return this trigger, so calls can be chained
@@ -122,7 +129,7 @@ public class Trigger implements BooleanSupplier {
}
/**
* Toggles a command when the signal rises from the low state to the high state.
* 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
@@ -143,7 +150,7 @@ public class Trigger implements BooleanSupplier {
}
/**
* Toggles a command when the signal rises from the low state to the high state.
* Toggles a command when the condition changes from `true` to the low state.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained

View File

@@ -4,6 +4,8 @@
#include "frc2/command/CommandPtr.h"
#include <frc/Errors.h>
#include "frc2/command/CommandScheduler.h"
#include "frc2/command/ConditionalCommand.h"
#include "frc2/command/InstantCommand.h"
@@ -20,12 +22,21 @@
using namespace frc2;
void CommandPtr::AssertValid() const {
if (!m_ptr) {
throw FRC_MakeError(frc::err::CommandIllegalUse,
"Moved-from CommandPtr object used!");
}
}
CommandPtr CommandPtr::Repeatedly() && {
AssertValid();
m_ptr = std::make_unique<RepeatCommand>(std::move(m_ptr));
return std::move(*this);
}
CommandPtr CommandPtr::AsProxy() && {
AssertValid();
m_ptr = std::make_unique<ProxyScheduleCommand>(std::move(m_ptr));
return std::move(*this);
}
@@ -44,6 +55,7 @@ class RunsWhenDisabledCommand : public WrapperCommand {
};
CommandPtr CommandPtr::IgnoringDisable(bool doesRunWhenDisabled) && {
AssertValid();
m_ptr = std::make_unique<RunsWhenDisabledCommand>(std::move(m_ptr),
doesRunWhenDisabled);
return std::move(*this);
@@ -67,6 +79,7 @@ class InterruptBehaviorCommand : public WrapperCommand {
CommandPtr CommandPtr::WithInterruptBehavior(
InterruptionBehavior interruptBehavior) && {
AssertValid();
m_ptr = std::make_unique<InterruptBehaviorCommand>(std::move(m_ptr),
interruptBehavior);
return std::move(*this);
@@ -74,6 +87,7 @@ CommandPtr CommandPtr::WithInterruptBehavior(
CommandPtr CommandPtr::AndThen(std::function<void()> toRun,
std::span<Subsystem* const> requirements) && {
AssertValid();
return std::move(*this).AndThen(CommandPtr(
std::make_unique<InstantCommand>(std::move(toRun), requirements)));
}
@@ -81,11 +95,13 @@ CommandPtr CommandPtr::AndThen(std::function<void()> toRun,
CommandPtr CommandPtr::AndThen(
std::function<void()> toRun,
std::initializer_list<Subsystem*> requirements) && {
AssertValid();
return std::move(*this).AndThen(CommandPtr(
std::make_unique<InstantCommand>(std::move(toRun), requirements)));
}
CommandPtr CommandPtr::AndThen(CommandPtr&& next) && {
AssertValid();
std::vector<std::unique_ptr<Command>> temp;
temp.emplace_back(std::move(m_ptr));
temp.emplace_back(std::move(next).Unwrap());
@@ -95,6 +111,7 @@ CommandPtr CommandPtr::AndThen(CommandPtr&& next) && {
CommandPtr CommandPtr::BeforeStarting(
std::function<void()> toRun, std::span<Subsystem* const> requirements) && {
AssertValid();
return std::move(*this).BeforeStarting(CommandPtr(
std::make_unique<InstantCommand>(std::move(toRun), requirements)));
}
@@ -102,11 +119,13 @@ CommandPtr CommandPtr::BeforeStarting(
CommandPtr CommandPtr::BeforeStarting(
std::function<void()> toRun,
std::initializer_list<Subsystem*> requirements) && {
AssertValid();
return std::move(*this).BeforeStarting(CommandPtr(
std::make_unique<InstantCommand>(std::move(toRun), requirements)));
}
CommandPtr CommandPtr::BeforeStarting(CommandPtr&& before) && {
AssertValid();
std::vector<std::unique_ptr<Command>> temp;
temp.emplace_back(std::move(before).Unwrap());
temp.emplace_back(std::move(m_ptr));
@@ -115,6 +134,7 @@ CommandPtr CommandPtr::BeforeStarting(CommandPtr&& before) && {
}
CommandPtr CommandPtr::WithTimeout(units::second_t duration) && {
AssertValid();
std::vector<std::unique_ptr<Command>> temp;
temp.emplace_back(std::make_unique<WaitCommand>(duration));
temp.emplace_back(std::move(m_ptr));
@@ -123,6 +143,7 @@ CommandPtr CommandPtr::WithTimeout(units::second_t duration) && {
}
CommandPtr CommandPtr::Until(std::function<bool()> condition) && {
AssertValid();
std::vector<std::unique_ptr<Command>> temp;
temp.emplace_back(std::make_unique<WaitUntilCommand>(std::move(condition)));
temp.emplace_back(std::move(m_ptr));
@@ -131,6 +152,7 @@ CommandPtr CommandPtr::Until(std::function<bool()> condition) && {
}
CommandPtr CommandPtr::Unless(std::function<bool()> condition) && {
AssertValid();
m_ptr = std::make_unique<ConditionalCommand>(
std::make_unique<InstantCommand>(), std::move(m_ptr),
std::move(condition));
@@ -138,6 +160,7 @@ CommandPtr CommandPtr::Unless(std::function<bool()> condition) && {
}
CommandPtr CommandPtr::DeadlineWith(CommandPtr&& parallel) && {
AssertValid();
std::vector<std::unique_ptr<Command>> vec;
vec.emplace_back(std::move(parallel).Unwrap());
m_ptr =
@@ -146,6 +169,7 @@ CommandPtr CommandPtr::DeadlineWith(CommandPtr&& parallel) && {
}
CommandPtr CommandPtr::AlongWith(CommandPtr&& parallel) && {
AssertValid();
std::vector<std::unique_ptr<Command>> vec;
vec.emplace_back(std::move(m_ptr));
vec.emplace_back(std::move(parallel).Unwrap());
@@ -154,6 +178,7 @@ CommandPtr CommandPtr::AlongWith(CommandPtr&& parallel) && {
}
CommandPtr CommandPtr::RaceWith(CommandPtr&& parallel) && {
AssertValid();
std::vector<std::unique_ptr<Command>> vec;
vec.emplace_back(std::move(m_ptr));
vec.emplace_back(std::move(parallel).Unwrap());
@@ -179,11 +204,13 @@ class FinallyCommand : public WrapperCommand {
} // namespace
CommandPtr CommandPtr::FinallyDo(std::function<void(bool)> end) && {
AssertValid();
m_ptr = std::make_unique<FinallyCommand>(std::move(m_ptr), std::move(end));
return std::move(*this);
}
CommandPtr CommandPtr::HandleInterrupt(std::function<void(void)> handler) && {
AssertValid();
return std::move(*this).FinallyDo(
[handler = std::move(handler)](bool interrupted) {
if (interrupted) {
@@ -193,29 +220,39 @@ CommandPtr CommandPtr::HandleInterrupt(std::function<void(void)> handler) && {
}
Command* CommandPtr::get() const {
AssertValid();
return m_ptr.get();
}
std::unique_ptr<Command> CommandPtr::Unwrap() && {
AssertValid();
return std::move(m_ptr);
}
void CommandPtr::Schedule() const {
AssertValid();
CommandScheduler::GetInstance().Schedule(*this);
}
void CommandPtr::Cancel() const {
AssertValid();
CommandScheduler::GetInstance().Cancel(*this);
}
bool CommandPtr::IsScheduled() const {
AssertValid();
return CommandScheduler::GetInstance().IsScheduled(*this);
}
bool CommandPtr::HasRequirement(Subsystem* requirement) const {
AssertValid();
return m_ptr->HasRequirement(requirement);
}
CommandPtr::operator bool() const {
return m_ptr.operator bool();
}
std::vector<std::unique_ptr<Command>> CommandPtr::UnwrapVector(
std::vector<CommandPtr>&& vec) {
std::vector<std::unique_ptr<Command>> ptrs;

View File

@@ -14,7 +14,6 @@
#include "frc2/command/PrintCommand.h"
#include "frc2/command/ProxyScheduleCommand.h"
#include "frc2/command/RunCommand.h"
#include "frc2/command/SelectCommand.h"
#include "frc2/command/SequentialCommandGroup.h"
#include "frc2/command/WaitCommand.h"
#include "frc2/command/WaitUntilCommand.h"
@@ -100,14 +99,6 @@ CommandPtr cmd::Either(CommandPtr&& onTrue, CommandPtr&& onFalse,
.ToPtr();
}
template <typename Key>
CommandPtr cmd::Select(std::function<Key()> selector,
std::vector<std::pair<Key, CommandPtr>> commands) {
return SelectCommand(std::move(selector),
CommandPtr::UnwrapVector(std::move(commands)))
.ToPtr();
}
CommandPtr cmd::Sequence(std::vector<CommandPtr>&& commands) {
return SequentialCommandGroup(CommandPtr::UnwrapVector(std::move(commands)))
.ToPtr();

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