mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-30 02:31:44 +00:00
Compare commits
73 Commits
v2023.1.1-
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf8faa9e67 | ||
|
|
5ec067c1f8 | ||
|
|
e962fd2916 | ||
|
|
88bd67e7de | ||
|
|
902e8686d3 | ||
|
|
e2d49181da | ||
|
|
149bac55b1 | ||
|
|
88f7a3ccb9 | ||
|
|
8acce443f0 | ||
|
|
295a1f8f3b | ||
|
|
388e7a4265 | ||
|
|
13aceea8dc | ||
|
|
c203f3f0a9 | ||
|
|
f54d495c90 | ||
|
|
e6392a1570 | ||
|
|
53904e7cf4 | ||
|
|
2e88a496c2 | ||
|
|
ce4c45df13 | ||
|
|
0401597d3b | ||
|
|
2e5f9e45bb | ||
|
|
e4b5795fc7 | ||
|
|
03d0ea188c | ||
|
|
3082bd236b | ||
|
|
b7ca860417 | ||
|
|
64838e6367 | ||
|
|
1269d2b901 | ||
|
|
14d8506b72 | ||
|
|
d1d458db2b | ||
|
|
f656e99245 | ||
|
|
6dd937cef7 | ||
|
|
49047c85b9 | ||
|
|
d07267fed1 | ||
|
|
b53ce1d3f0 | ||
|
|
5a320c326b | ||
|
|
c4e526d315 | ||
|
|
d122e4254f | ||
|
|
5a1e7ea036 | ||
|
|
179f569113 | ||
|
|
b0f6dc199d | ||
|
|
7836f661cd | ||
|
|
dbcc1de37f | ||
|
|
93890c528b | ||
|
|
3d8d5936f9 | ||
|
|
2b04159dec | ||
|
|
2764004fad | ||
|
|
85f1bb8f2b | ||
|
|
231ae2c353 | ||
|
|
e92b6dd5f9 | ||
|
|
2a8e0e1cc8 | ||
|
|
7d06e517e9 | ||
|
|
323524fed6 | ||
|
|
d426873ed1 | ||
|
|
5be5869b2f | ||
|
|
b1b4c1e9e7 | ||
|
|
a4054d702f | ||
|
|
0190301e09 | ||
|
|
9d1ce6a6d9 | ||
|
|
5005e2ca04 | ||
|
|
fa44a07938 | ||
|
|
4ba16db645 | ||
|
|
837415abfd | ||
|
|
2c20fd0d09 | ||
|
|
64a7136e08 | ||
|
|
b2b473b24a | ||
|
|
7aab8fa93a | ||
|
|
12c2851856 | ||
|
|
0da169dd84 | ||
|
|
2416827c25 | ||
|
|
1177a3522e | ||
|
|
102344e27a | ||
|
|
1831ef3e19 | ||
|
|
a9606ce870 | ||
|
|
6c80d5eab3 |
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@@ -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: ""
|
||||
|
||||
11
.github/workflows/comment-command.yml
vendored
11
.github/workflows/comment-command.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/gazebo.yml
vendored
2
.github/workflows/gazebo.yml
vendored
@@ -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
|
||||
|
||||
11
.github/workflows/gradle.yml
vendored
11
.github/workflows/gradle.yml
vendored
@@ -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:
|
||||
|
||||
14
.github/workflows/lint-format.yml
vendored
14
.github/workflows/lint-format.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/sanitizers.yml
vendored
2
.github/workflows/sanitizers.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/upstream-utils.yml
vendored
6
.github/workflows/upstream-utils.yml
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# WPILib Project
|
||||
|
||||

|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/cpp/)
|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/java/)
|
||||
[](https://github.wpilib.org/allwpilib/docs/development/cpp/)
|
||||
[](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
41
apriltag/CMakeLists.txt
Normal 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
77
apriltag/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
apriltag/src/dev/java/edu/wpi/first/apriltag/DevMain.java
Normal file
14
apriltag/src/dev/java/edu/wpi/first/apriltag/DevMain.java
Normal 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() {}
|
||||
}
|
||||
5
apriltag/src/dev/native/cpp/main.cpp
Normal file
5
apriltag/src/dev/native/cpp/main.cpp
Normal 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() {}
|
||||
47
apriltag/src/main/java/edu/wpi/first/apriltag/AprilTag.java
Normal file
47
apriltag/src/main/java/edu/wpi/first/apriltag/AprilTag.java
Normal 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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
26
apriltag/src/main/native/cpp/AprilTag.cpp
Normal file
26
apriltag/src/main/native/cpp/AprilTag.cpp
Normal 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>();
|
||||
}
|
||||
117
apriltag/src/main/native/cpp/AprilTagFieldLayout.cpp
Normal file
117
apriltag/src/main/native/cpp/AprilTagFieldLayout.cpp
Normal 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>()};
|
||||
}
|
||||
28
apriltag/src/main/native/cpp/AprilTagFields.cpp
Normal file
28
apriltag/src/main/native/cpp/AprilTagFields.cpp
Normal 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
|
||||
45
apriltag/src/main/native/include/frc/apriltag/AprilTag.h
Normal file
45
apriltag/src/main/native/include/frc/apriltag/AprilTag.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
27
apriltag/src/test/native/cpp/AprilTagJsonTest.cpp
Normal file
27
apriltag/src/test/native/cpp/AprilTagJsonTest.cpp
Normal 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);
|
||||
}
|
||||
33
apriltag/src/test/native/cpp/AprilTagPoseSetOriginTest.cpp
Normal file
33
apriltag/src/test/native/cpp/AprilTagPoseSetOriginTest.cpp
Normal 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));
|
||||
}
|
||||
61
apriltag/src/test/native/cpp/LoadConfigTest.cpp
Normal file
61
apriltag/src/test/native/cpp/LoadConfigTest.cpp
Normal 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
|
||||
11
apriltag/src/test/native/cpp/main.cpp
Normal file
11
apriltag/src/test/native/cpp/main.cpp
Normal 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;
|
||||
}
|
||||
10
build.gradle
10
build.gradle
@@ -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)) {
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2023.4.0"
|
||||
implementation "edu.wpi.first:native-utils:2023.8.0"
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>^^',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -326,6 +326,7 @@ void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
|
||||
if (gShutdown) {
|
||||
return;
|
||||
}
|
||||
hal::init::CheckInit();
|
||||
driverStation->newDataEvents.Add(handle);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ dependencies {
|
||||
implementation project(':wpilibNewCommands')
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {
|
||||
onlyIf { false }
|
||||
}
|
||||
|
||||
def simProjects = ['halsim_gui']
|
||||
|
||||
deploy {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
49
ntcore/src/main/native/cpp/Value_internal.cpp
Normal file
49
ntcore/src/main/native/cpp/Value_internal.cpp
Normal 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 {};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -52,6 +52,7 @@ include 'myRobot'
|
||||
include 'docs'
|
||||
include 'msvcruntime'
|
||||
include 'ntcoreffi'
|
||||
include 'apriltag'
|
||||
|
||||
buildCache {
|
||||
def cred = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
158
upstream_utils/mpack_patches/0003-Use-namespace-for-C.patch
Normal file
158
upstream_utils/mpack_patches/0003-Use-namespace-for-C.patch
Normal 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 */
|
||||
|
||||
/**
|
||||
@@ -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):
|
||||
|
||||
@@ -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
59
upstream_utils/update_mpack.py
Executable 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()
|
||||
@@ -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@
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"windowsx86-64",
|
||||
"windowsx86",
|
||||
"linuxx86-64",
|
||||
"osxx86-64",
|
||||
"osxarm64"
|
||||
"osxuniversal"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user