mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
Compare commits
88 Commits
v2023.1.1-
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a52b51443 | ||
|
|
69a66ec5ec | ||
|
|
989c9fb29a | ||
|
|
0f5b08ec69 | ||
|
|
fba191099c | ||
|
|
b390cad095 | ||
|
|
b9772214d9 | ||
|
|
342c375a71 | ||
|
|
b0e4053087 | ||
|
|
f3e666b7bb | ||
|
|
b300518bd1 | ||
|
|
be27171236 | ||
|
|
4bbdbdfb48 | ||
|
|
f18fd41ac3 | ||
|
|
2d0faecf4f | ||
|
|
348bd107fc | ||
|
|
3149dc64b8 | ||
|
|
8618dd4160 | ||
|
|
72e21a1ed1 | ||
|
|
eab0d929e6 | ||
|
|
6789869663 | ||
|
|
c9dea2968d | ||
|
|
8f402645f5 | ||
|
|
f24ad1d715 | ||
|
|
ff88756864 | ||
|
|
f58873db8e | ||
|
|
37e969b41a | ||
|
|
13cdc29382 | ||
|
|
6e23985ae6 | ||
|
|
66bb0ffb2c | ||
|
|
74cc86c4c5 | ||
|
|
e22d8cc343 | ||
|
|
68dba92630 | ||
|
|
23bfc2d9ab | ||
|
|
1f1461e254 | ||
|
|
eae68fc165 | ||
|
|
4c4545fb4b | ||
|
|
16ffaa754d | ||
|
|
5e74ff26d8 | ||
|
|
53875419a1 | ||
|
|
aa6499e920 | ||
|
|
df70351107 | ||
|
|
e9bd50ff9b | ||
|
|
9b319fd56b | ||
|
|
18d28ec5e3 | ||
|
|
bdfb625211 | ||
|
|
21003e34eb | ||
|
|
70080457d5 | ||
|
|
e82cd5147b | ||
|
|
ec124bb662 | ||
|
|
2b2aa8eef7 | ||
|
|
cb38bacfe8 | ||
|
|
15561338d5 | ||
|
|
ca35a2e097 | ||
|
|
20dbae0cee | ||
|
|
1a59737f40 | ||
|
|
42b6d4e3f7 | ||
|
|
135c13958f | ||
|
|
ffbfc61532 | ||
|
|
8958b2a4da | ||
|
|
e4ac09077c | ||
|
|
f40de0c120 | ||
|
|
51fa3e851f | ||
|
|
1da84b2255 | ||
|
|
e43e2fbc84 | ||
|
|
5804d8fa84 | ||
|
|
169ef5fabf | ||
|
|
148759ef54 | ||
|
|
58ed112b51 | ||
|
|
dd1da77d20 | ||
|
|
7cda85df20 | ||
|
|
7ed9b13277 | ||
|
|
6b4f26225d | ||
|
|
b2d2924b72 | ||
|
|
34ec89c041 | ||
|
|
e15200068d | ||
|
|
d5200db6cd | ||
|
|
2ee3d86de4 | ||
|
|
9f0a8b930f | ||
|
|
2bca43779e | ||
|
|
4307d0ee8b | ||
|
|
3fe8d355a1 | ||
|
|
b44034dadc | ||
|
|
52d2c53888 | ||
|
|
76e918f71e | ||
|
|
0bee875aff | ||
|
|
98e922313b | ||
|
|
9a36373b8f |
6
.github/workflows/comment-command.yml
vendored
6
.github/workflows/comment-command.yml
vendored
@@ -4,8 +4,8 @@ on:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
wpiformat:
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
|
||||
format:
|
||||
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/format')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: React Rocket
|
||||
@@ -60,5 +60,5 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
# Commit
|
||||
git commit -am "wpiformat"
|
||||
git commit -am "Formatting fixes"
|
||||
git push
|
||||
|
||||
19
.github/workflows/gazebo.yml
vendored
19
.github/workflows/gazebo.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Gazebo
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/gazebo-ubuntu:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew simulation:frc_gazebo_plugins:build simulation:halsim_gazebo:build -PbuildServer -PforceGazebo
|
||||
2
.github/workflows/gradle.yml
vendored
2
.github/workflows/gradle.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
architecture: ${{ matrix.architecture }}
|
||||
- name: Import Developer ID Certificate
|
||||
uses: wpilibsuite/import-signing-certificate@v1
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,6 +5,10 @@ doxygen.log
|
||||
build*/
|
||||
!buildSrc/
|
||||
|
||||
simgui-ds.json
|
||||
simgui-window.json
|
||||
simgui.json
|
||||
|
||||
# Created by the jenkins test script
|
||||
test-reports
|
||||
|
||||
|
||||
30
README.md
30
README.md
@@ -1,6 +1,6 @@
|
||||
# WPILib Project
|
||||
|
||||

|
||||
[](https://github.com/wpilibsuite/allwpilib/actions/workflows/gradle.yml)
|
||||
[](https://github.wpilib.org/allwpilib/docs/development/cpp/)
|
||||
[](https://github.wpilib.org/allwpilib/docs/development/java/)
|
||||
|
||||
@@ -15,7 +15,6 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
|
||||
- [Faster builds](#faster-builds)
|
||||
- [Using Development Builds](#using-development-builds)
|
||||
- [Custom toolchain location](#custom-toolchain-location)
|
||||
- [Gazebo simulation](#gazebo-simulation)
|
||||
- [Formatting/Linting](#formattinglinting)
|
||||
- [CMake](#cmake)
|
||||
- [Publishing](#publishing)
|
||||
@@ -114,33 +113,14 @@ If you have installed the FRC Toolchain to a directory other than the default, o
|
||||
./gradlew build -PtoolChainPath=some/path/to/frc/toolchain/bin
|
||||
```
|
||||
|
||||
### Gazebo simulation
|
||||
|
||||
If you also want to force building Gazebo simulation support, add -PforceGazebo. This requires gazebo_transport. We have tested on 14.04 and 15.05, but any correct install of Gazebo should work, even on Windows if you build Gazebo from source. Correct means CMake needs to be able to find gazebo-config.cmake. See [The Gazebo website](https://gazebosim.org/) for installation instructions.
|
||||
|
||||
```bash
|
||||
./gradlew build -PforceGazebo
|
||||
```
|
||||
|
||||
If you prefer to use CMake directly, the you can still do so.
|
||||
The common CMake tasks are wpilibcSim, frc_gazebo_plugins, and gz_msgs
|
||||
|
||||
```bash
|
||||
mkdir build #run this in the root of allwpilib
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
### Formatting/linting
|
||||
|
||||
Once a PR has been submitted, formatting can be run in CI by commenting `/format` on the PR. A new commit will be pushed with the formatting changes.
|
||||
|
||||
#### wpiformat
|
||||
|
||||
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat -clang 14` on Windows or `python3 -m wpiformat -clang 14` on other platforms.
|
||||
|
||||
Once a PR has been submitted, formatting can be run in CI by commenting `/wpiformat` on the PR. A new commit will be pushed with the formatting changes.
|
||||
|
||||
#### Java Code Quality Tools
|
||||
|
||||
The Java code quality tools Checkstyle, PMD, and Spotless can be run via `./gradlew javaFormat`. SpotBugs can be run via the `spotbugsMain`, `spotbugsTest`, and `spotbugsDev` tasks. These tools will all be run automatically by the `build` task. To disable this behavior, pass the `-PskipJavaFormat` flag.
|
||||
@@ -164,9 +144,7 @@ The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
|
||||
|
||||
## Structure and Organization
|
||||
|
||||
The main WPILib code you're probably looking for is in WPILibJ and WPILibC. Those directories are split into shared, sim, and athena. Athena contains the WPILib code meant to run on your roboRIO. Sim is WPILib code meant to run on your computer with Gazebo, and shared is code shared between the two. Shared code must be platform-independent, since it will be compiled with both the ARM cross-compiler and whatever desktop compiler you are using (g++, msvc, etc...).
|
||||
|
||||
The Simulation directory contains extra simulation tools and libraries, such as gz_msgs and JavaGazebo. See sub-directories for more information.
|
||||
The main WPILib code you're probably looking for is in WPILibJ and WPILibC. Those directories are split into shared, sim, and athena. Athena contains the WPILib code meant to run on your roboRIO. Sim is WPILib code meant to run on your computer, and shared is code shared between the two. Shared code must be platform-independent, since it will be compiled with both the ARM cross-compiler and whatever desktop compiler you are using (g++, msvc, etc...).
|
||||
|
||||
The integration test directories for C++ and Java contain test code that runs on our test-system. When you submit code for review, it is tested by those programs. If you add new functionality you should make sure to write tests for it so we don't break it in the future.
|
||||
|
||||
|
||||
@@ -1,41 +1,118 @@
|
||||
project(fieldImages)
|
||||
project(apriltag)
|
||||
|
||||
include(CompileWarnings)
|
||||
include(GenResources)
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
apriltaglib
|
||||
GIT_REPOSITORY https://github.com/wpilibsuite/apriltag.git
|
||||
GIT_TAG ad31e33d20f9782b7239cb15cde57c56c91383ad
|
||||
)
|
||||
|
||||
# Don't use apriltag's CMakeLists.txt due to conflicting naming and JNI
|
||||
FetchContent_GetProperties(apriltaglib)
|
||||
if(NOT apriltaglib_POPULATED)
|
||||
FetchContent_Populate(apriltaglib)
|
||||
endif()
|
||||
|
||||
aux_source_directory(${apriltaglib_SOURCE_DIR}/common APRILTAGLIB_COMMON_SRC)
|
||||
file(GLOB TAG_FILES ${apriltaglib_SOURCE_DIR}/tag*.c)
|
||||
set(APRILTAGLIB_SRCS ${apriltaglib_SOURCE_DIR}/apriltag.c ${apriltaglib_SOURCE_DIR}/apriltag_pose.c ${apriltaglib_SOURCE_DIR}/apriltag_quad_thresh.c)
|
||||
|
||||
file(GLOB apriltag_jni_src src/main/native/cpp/jni/AprilTagJNI.cpp)
|
||||
|
||||
if (WITH_JAVA)
|
||||
find_package(Java REQUIRED)
|
||||
include(UseJava)
|
||||
find_package(Java REQUIRED)
|
||||
find_package(JNI REQUIRED)
|
||||
include(UseJava)
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
|
||||
|
||||
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
|
||||
set(CMAKE_JNI_TARGET true)
|
||||
|
||||
set(CMAKE_JAVA_INCLUDE_PATH apriltags.jar ${JACKSON_JARS})
|
||||
file(GLOB EJML_JARS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/*.jar")
|
||||
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
|
||||
find_file(OPENCV_JAR_FILE
|
||||
NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar
|
||||
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/share/java
|
||||
NO_DEFAULT_PATH)
|
||||
|
||||
set(CMAKE_JAVA_INCLUDE_PATH apriltag.jar ${EJML_JARS} ${JACKSON_JARS})
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
|
||||
add_jar(apriltag_jar
|
||||
SOURCES ${JAVA_SOURCES}
|
||||
RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES}
|
||||
INCLUDE_JARS wpimath_jar ${EJML_JARS} wpiutil_jar ${OPENCV_JAR_FILE}
|
||||
OUTPUT_NAME apriltag
|
||||
GENERATE_NATIVE_HEADERS apriltag_jni_headers)
|
||||
|
||||
get_property(APRILTAG_JAR_FILE TARGET apriltag_jar PROPERTY JAR_FILE)
|
||||
install(FILES ${APRILTAG_JAR_FILE} DESTINATION "${java_lib_dest}")
|
||||
|
||||
set_property(TARGET apriltag_jar PROPERTY FOLDER "java")
|
||||
|
||||
add_library(apriltagjni ${apriltag_jni_src})
|
||||
wpilib_target_warnings(apriltagjni)
|
||||
target_link_libraries(apriltagjni PUBLIC apriltag)
|
||||
|
||||
set_property(TARGET apriltagjni PROPERTY FOLDER "libraries")
|
||||
|
||||
target_link_libraries(apriltagjni PRIVATE apriltag_jni_headers)
|
||||
add_dependencies(apriltagjni apriltag_jar)
|
||||
|
||||
if (MSVC)
|
||||
install(TARGETS apriltagjni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
install(TARGETS apriltagjni EXPORT apriltagjni DESTINATION "${main_lib_dest}")
|
||||
|
||||
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 apriltag_resources_src)
|
||||
|
||||
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltags_resources_src)
|
||||
file(GLOB apriltag_native_src src/main/native/cpp/*.cpp)
|
||||
|
||||
file(GLOB_RECURSE apriltags_native_src src/main/native/cpp/*.cpp)
|
||||
add_library(apriltag ${apriltag_native_src} ${apriltag_resources_src} ${APRILTAGLIB_SRCS} ${APRILTAGLIB_COMMON_SRC} ${TAG_FILES})
|
||||
set_target_properties(apriltag PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
add_library(apriltags ${apriltags_native_src} ${apriltags_resources_src})
|
||||
set_target_properties(apriltags PROPERTIES DEBUG_POSTFIX "d")
|
||||
set_property(TARGET apriltag PROPERTY FOLDER "libraries")
|
||||
target_compile_features(apriltag PUBLIC cxx_std_20)
|
||||
wpilib_target_warnings(apriltag)
|
||||
# disable warnings that apriltaglib can't handle
|
||||
if (MSVC)
|
||||
target_compile_options(apriltag PRIVATE /wd4018)
|
||||
else()
|
||||
target_compile_options(apriltag PRIVATE -Wno-sign-compare -Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
|
||||
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_link_libraries(apriltag wpimath)
|
||||
|
||||
target_include_directories(apriltags PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/apriltags>)
|
||||
target_include_directories(apriltag PUBLIC
|
||||
$<BUILD_INTERFACE:${apriltaglib_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
|
||||
$<INSTALL_INTERFACE:${include_dest}/apriltag>)
|
||||
|
||||
install(TARGETS apriltag EXPORT apriltag DESTINATION "${main_lib_dest}")
|
||||
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/apriltag")
|
||||
|
||||
if (WITH_JAVA AND MSVC)
|
||||
install(TARGETS apriltag RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
|
||||
endif()
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (apriltag_config_dir ${wpilib_dest})
|
||||
else()
|
||||
set (apriltag_config_dir share/apriltag)
|
||||
endif()
|
||||
|
||||
configure_file(apriltag-config.cmake.in ${WPILIB_BINARY_DIR}/apriltag-config.cmake )
|
||||
install(FILES ${WPILIB_BINARY_DIR}/apriltag-config.cmake DESTINATION ${apriltag_config_dir})
|
||||
install(EXPORT apriltag DESTINATION ${apriltag_config_dir})
|
||||
|
||||
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)
|
||||
wpilib_add_test(apriltag src/test/native/cpp)
|
||||
target_include_directories(apriltag_test PRIVATE src/test/native/include)
|
||||
target_link_libraries(apriltag_test apriltag gmock_main)
|
||||
endif()
|
||||
|
||||
7
apriltag/apriltag-config.cmake.in
Normal file
7
apriltag/apriltag-config.cmake.in
Normal file
@@ -0,0 +1,7 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
@FILENAME_DEP_REPLACE@
|
||||
@WPIMATH_DEP_REPLACE@
|
||||
@WPIUTIL_DEP_REPLACE@
|
||||
|
||||
@FILENAME_DEP_REPLACE@
|
||||
include(${SELF_DIR}/apriltag.cmake)
|
||||
@@ -1,15 +1,16 @@
|
||||
apply from: "${rootDir}/shared/resources.gradle"
|
||||
|
||||
ext {
|
||||
nativeName = 'apriltags'
|
||||
nativeName = 'apriltag'
|
||||
devMain = 'edu.wpi.first.apriltag.DevMain'
|
||||
useJava = true
|
||||
|
||||
def generateTask = createGenerateResourcesTask('main', 'APRILTAGS', 'frc', project)
|
||||
def generateTask = createGenerateResourcesTask('main', 'APRILTAG', 'frc', project)
|
||||
|
||||
tasks.withType(CppCompile) {
|
||||
dependsOn generateTask
|
||||
}
|
||||
extraSetup = {
|
||||
splitSetup = {
|
||||
it.sources {
|
||||
resourcesCpp(CppSourceSet) {
|
||||
source {
|
||||
@@ -23,7 +24,9 @@ ext {
|
||||
|
||||
evaluationDependsOn(':wpimath')
|
||||
|
||||
apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
|
||||
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
|
||||
apply from: "${rootDir}/shared/apriltaglib.gradle"
|
||||
apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation project(':wpimath')
|
||||
@@ -47,12 +50,15 @@ model {
|
||||
}
|
||||
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')
|
||||
if (it.component.name == "${nativeName}JNI") {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
} else {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
|
||||
nativeUtils.useRequiredLibrary(it, 'apriltaglib')
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.apriltag.jni;
|
||||
|
||||
import edu.wpi.first.util.RuntimeLoader;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class AprilTagJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
|
||||
static RuntimeLoader<AprilTagJNI> loader = null;
|
||||
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
if (Helper.getExtractOnStaticLoad()) {
|
||||
try {
|
||||
loader =
|
||||
new RuntimeLoader<>(
|
||||
"apriltagjni", RuntimeLoader.getDefaultExtractionRoot(), AprilTagJNI.class);
|
||||
loader.loadLibrary();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to a apriltag_detector_t
|
||||
public static native long aprilTagCreate(
|
||||
String fam, double decimate, double blur, int threads, boolean debug, boolean refine_edges);
|
||||
|
||||
// Destroy and free a previously created detector.
|
||||
public static native void aprilTagDestroy(long detector);
|
||||
|
||||
private static native Object[] aprilTagDetectInternal(
|
||||
long detector,
|
||||
long imgAddr,
|
||||
int rows,
|
||||
int cols,
|
||||
boolean doPoseEstimation,
|
||||
double tagWidth,
|
||||
double fx,
|
||||
double fy,
|
||||
double cx,
|
||||
double cy,
|
||||
int nIters);
|
||||
|
||||
// Detect targets given a GRAY frame. Returns a pointer toa zarray
|
||||
public static DetectionResult[] aprilTagDetect(
|
||||
long detector,
|
||||
Mat img,
|
||||
boolean doPoseEstimation,
|
||||
double tagWidth,
|
||||
double fx,
|
||||
double fy,
|
||||
double cx,
|
||||
double cy,
|
||||
int nIters) {
|
||||
return (DetectionResult[])
|
||||
aprilTagDetectInternal(
|
||||
detector,
|
||||
img.dataAddr(),
|
||||
img.rows(),
|
||||
img.cols(),
|
||||
doPoseEstimation,
|
||||
tagWidth,
|
||||
fx,
|
||||
fy,
|
||||
cx,
|
||||
cy,
|
||||
nIters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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.jni;
|
||||
|
||||
import edu.wpi.first.math.MatBuilder;
|
||||
import edu.wpi.first.math.Matrix;
|
||||
import edu.wpi.first.math.Nat;
|
||||
import edu.wpi.first.math.geometry.Rotation3d;
|
||||
import edu.wpi.first.math.geometry.Transform3d;
|
||||
import edu.wpi.first.math.geometry.Translation3d;
|
||||
import edu.wpi.first.math.numbers.N3;
|
||||
import java.util.Arrays;
|
||||
import org.ejml.data.DMatrixRMaj;
|
||||
import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
|
||||
import org.ejml.simple.SimpleMatrix;
|
||||
|
||||
public class DetectionResult {
|
||||
public int getId() {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
public int getHamming() {
|
||||
return m_hamming;
|
||||
}
|
||||
|
||||
public float getDecisionMargin() {
|
||||
return m_decisionMargin;
|
||||
}
|
||||
|
||||
public void setDecisionMargin(float decisionMargin) {
|
||||
this.m_decisionMargin = decisionMargin;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.MethodReturnsInternalArray")
|
||||
public double[] getHomography() {
|
||||
return m_homography;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public void setHomography(double[] homography) {
|
||||
this.m_homography = homography;
|
||||
}
|
||||
|
||||
public double getCenterX() {
|
||||
return m_centerX;
|
||||
}
|
||||
|
||||
public void setCenterX(double centerX) {
|
||||
this.m_centerX = centerX;
|
||||
}
|
||||
|
||||
public double getCenterY() {
|
||||
return m_centerY;
|
||||
}
|
||||
|
||||
public void setCenterY(double centerY) {
|
||||
this.m_centerY = centerY;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.MethodReturnsInternalArray")
|
||||
public double[] getCorners() {
|
||||
return m_corners;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public void setCorners(double[] corners) {
|
||||
this.m_corners = corners;
|
||||
}
|
||||
|
||||
public double getError1() {
|
||||
return m_error1;
|
||||
}
|
||||
|
||||
public double getError2() {
|
||||
return m_error2;
|
||||
}
|
||||
|
||||
public Transform3d getPoseResult1() {
|
||||
return m_poseResult1;
|
||||
}
|
||||
|
||||
public Transform3d getPoseResult2() {
|
||||
return m_poseResult2;
|
||||
}
|
||||
|
||||
private final int m_id;
|
||||
private final int m_hamming;
|
||||
private float m_decisionMargin;
|
||||
private double[] m_homography;
|
||||
private double m_centerX;
|
||||
private double m_centerY;
|
||||
private double[] m_corners;
|
||||
|
||||
private final Transform3d m_poseResult1;
|
||||
private final double m_error1;
|
||||
private final Transform3d m_poseResult2;
|
||||
private final double m_error2;
|
||||
|
||||
/**
|
||||
* Constructs a new detection result. Used from JNI.
|
||||
*
|
||||
* @param id id
|
||||
* @param hamming hamming
|
||||
* @param decisionMargin dm
|
||||
* @param homography homography
|
||||
* @param centerX centerX
|
||||
* @param centerY centerY
|
||||
* @param corners corners
|
||||
* @param pose1TransArr pose1TransArr
|
||||
* @param pose1RotArr pose1RotArr
|
||||
* @param err1 err1
|
||||
* @param pose2TransArr pose2TransArr
|
||||
* @param pose2RotArr pose2RotArr
|
||||
* @param err2 err2
|
||||
*/
|
||||
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
|
||||
public DetectionResult(
|
||||
int id,
|
||||
int hamming,
|
||||
float decisionMargin,
|
||||
double[] homography,
|
||||
double centerX,
|
||||
double centerY,
|
||||
double[] corners,
|
||||
double[] pose1TransArr,
|
||||
double[] pose1RotArr,
|
||||
double err1,
|
||||
double[] pose2TransArr,
|
||||
double[] pose2RotArr,
|
||||
double err2) {
|
||||
this.m_id = id;
|
||||
this.m_hamming = hamming;
|
||||
this.m_decisionMargin = decisionMargin;
|
||||
this.m_homography = homography;
|
||||
this.m_centerX = centerX;
|
||||
this.m_centerY = centerY;
|
||||
this.m_corners = corners;
|
||||
|
||||
this.m_error1 = err1;
|
||||
this.m_poseResult1 =
|
||||
new Transform3d(
|
||||
new Translation3d(pose1TransArr[0], pose1TransArr[1], pose1TransArr[2]),
|
||||
new Rotation3d(
|
||||
orthogonalizeRotationMatrix(
|
||||
new MatBuilder<>(Nat.N3(), Nat.N3()).fill(pose1RotArr))));
|
||||
this.m_error2 = err2;
|
||||
this.m_poseResult2 =
|
||||
new Transform3d(
|
||||
new Translation3d(pose2TransArr[0], pose2TransArr[1], pose2TransArr[2]),
|
||||
new Rotation3d(
|
||||
orthogonalizeRotationMatrix(
|
||||
new MatBuilder<>(Nat.N3(), Nat.N3()).fill(pose2RotArr))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ratio of pose reprojection errors, called ambiguity. Numbers above 0.2 are likely to be
|
||||
* ambiguous.
|
||||
*
|
||||
* @return The ratio of pose reprojection errors.
|
||||
*/
|
||||
public double getPoseAmbiguity() {
|
||||
var min = Math.min(m_error1, m_error2);
|
||||
var max = Math.max(m_error1, m_error2);
|
||||
|
||||
if (max > 0) {
|
||||
return min / max;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DetectionResult [centerX="
|
||||
+ m_centerX
|
||||
+ ", centerY="
|
||||
+ m_centerY
|
||||
+ ", corners="
|
||||
+ Arrays.toString(m_corners)
|
||||
+ ", decisionMargin="
|
||||
+ m_decisionMargin
|
||||
+ ", error1="
|
||||
+ m_error1
|
||||
+ ", error2="
|
||||
+ m_error2
|
||||
+ ", hamming="
|
||||
+ m_hamming
|
||||
+ ", homography="
|
||||
+ Arrays.toString(m_homography)
|
||||
+ ", id="
|
||||
+ m_id
|
||||
+ ", poseResult1="
|
||||
+ m_poseResult1
|
||||
+ ", poseResult2="
|
||||
+ m_poseResult2
|
||||
+ "]";
|
||||
}
|
||||
|
||||
private static Matrix<N3, N3> orthogonalizeRotationMatrix(Matrix<N3, N3> input) {
|
||||
var a = DecompositionFactory_DDRM.qr(3, 3);
|
||||
if (!a.decompose(input.getStorage().getDDRM())) {
|
||||
// best we can do is return the input
|
||||
return input;
|
||||
}
|
||||
|
||||
// Grab results (thanks for this _great_ api, EJML)
|
||||
var Q = new DMatrixRMaj(3, 3);
|
||||
var R = new DMatrixRMaj(3, 3);
|
||||
a.getQ(Q, false);
|
||||
a.getR(R, false);
|
||||
|
||||
// Fix signs in R if they're < 0 so it's close to an identity matrix
|
||||
// (our QR decomposition implementation sometimes flips the signs of columns)
|
||||
for (int colR = 0; colR < 3; ++colR) {
|
||||
if (R.get(colR, colR) < 0) {
|
||||
for (int rowQ = 0; rowQ < 3; ++rowQ) {
|
||||
Q.set(rowQ, colR, -Q.get(rowQ, colR));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix<>(new SimpleMatrix(Q));
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,6 @@
|
||||
|
||||
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}};
|
||||
}
|
||||
|
||||
@@ -81,16 +81,6 @@ void AprilTagFieldLayout::Serialize(std::string_view path) {
|
||||
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());
|
||||
|
||||
320
apriltag/src/main/native/cpp/jni/AprilTagJNI.cpp
Normal file
320
apriltag/src/main/native/cpp/jni/AprilTagJNI.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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 <wpi/jni_util.h>
|
||||
|
||||
#include "edu_wpi_first_apriltag_jni_AprilTagJNI.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4200)
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#endif
|
||||
#include "apriltag.h"
|
||||
#ifdef _WIN32
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "tag36h11.h"
|
||||
#include "tag25h9.h"
|
||||
#include "tag16h5.h"
|
||||
#include "tagCircle21h7.h"
|
||||
#include "tagCircle49h12.h"
|
||||
#include "tagCustom48h12.h"
|
||||
#include "tagStandard41h12.h"
|
||||
#include "tagStandard52h13.h"
|
||||
#include "apriltag_pose.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace wpi::java;
|
||||
|
||||
struct DetectorState {
|
||||
int id;
|
||||
apriltag_detector_t* td;
|
||||
apriltag_family_t* tf;
|
||||
void (*tf_destroy)(apriltag_family_t*);
|
||||
};
|
||||
|
||||
static std::vector<DetectorState> detectors;
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
|
||||
* Method: aprilTagCreate
|
||||
* Signature: (Ljava/lang/String;DDIZZ)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagCreate
|
||||
(JNIEnv* env, jclass cls, jstring jstr, jdouble decimate, jdouble blur,
|
||||
jint threads, jboolean debug, jboolean refine_edges)
|
||||
{
|
||||
// Initialize tag detector with options
|
||||
apriltag_family_t* tf = nullptr;
|
||||
// const char *famname = fam;
|
||||
const char* famname = env->GetStringUTFChars(jstr, nullptr);
|
||||
|
||||
void (*tf_destroy_func)(apriltag_family_t*);
|
||||
|
||||
if (!strcmp(famname, "tag36h11")) {
|
||||
tf = tag36h11_create();
|
||||
tf_destroy_func = tag36h11_destroy;
|
||||
} else if (!strcmp(famname, "tag25h9")) {
|
||||
tf = tag25h9_create();
|
||||
tf_destroy_func = tag25h9_destroy;
|
||||
} else if (!strcmp(famname, "tag16h5")) {
|
||||
tf = tag16h5_create();
|
||||
tf_destroy_func = tag16h5_destroy;
|
||||
} else if (!strcmp(famname, "tagCircle21h7")) {
|
||||
tf = tagCircle21h7_create();
|
||||
tf_destroy_func = tagCircle21h7_destroy;
|
||||
} else if (!strcmp(famname, "tagCircle49h12")) {
|
||||
tf = tagCircle49h12_create();
|
||||
tf_destroy_func = tagCircle49h12_destroy;
|
||||
} else if (!strcmp(famname, "tagStandard41h12")) {
|
||||
tf = tagStandard41h12_create();
|
||||
tf_destroy_func = tagStandard41h12_destroy;
|
||||
} else if (!strcmp(famname, "tagStandard52h13")) {
|
||||
tf = tagStandard52h13_create();
|
||||
tf_destroy_func = tagStandard52h13_destroy;
|
||||
} else if (!strcmp(famname, "tagCustom48h12")) {
|
||||
tf = tagCustom48h12_create();
|
||||
tf_destroy_func = tagCustom48h12_destroy;
|
||||
} else {
|
||||
std::printf("Unrecognized tag family name. Use e.g. \"tag36h11\".\n");
|
||||
env->ReleaseStringUTFChars(jstr, famname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
apriltag_detector_t* td = apriltag_detector_create();
|
||||
apriltag_detector_add_family(td, tf);
|
||||
td->quad_decimate = static_cast<float>(decimate);
|
||||
td->quad_sigma = static_cast<float>(blur);
|
||||
td->nthreads = threads;
|
||||
td->debug = debug;
|
||||
td->refine_edges = refine_edges;
|
||||
|
||||
env->ReleaseStringUTFChars(jstr, famname);
|
||||
|
||||
// std::printf("Looking for max\n");
|
||||
auto max = std::max_element(detectors.begin(), detectors.end(),
|
||||
[](DetectorState& a, DetectorState& b) {
|
||||
return a.id < b.id;
|
||||
}); // detectors.size();
|
||||
int index = 0;
|
||||
if (max != detectors.end())
|
||||
index = max->id + 1;
|
||||
detectors.push_back({index, td, tf, tf_destroy_func});
|
||||
std::printf("Created detector at idx %i\n", index);
|
||||
return (jlong)index;
|
||||
}
|
||||
|
||||
static JClass detectionClass;
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
detectionClass = JClass(env, "edu/wpi/first/apriltag/jni/DetectionResult");
|
||||
|
||||
if (!detectionClass) {
|
||||
std::printf("Couldn't find class!");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
static jobject MakeJObject(JNIEnv* env, const apriltag_detection_t* detect,
|
||||
apriltag_pose_t& pose1, apriltag_pose_t& pose2,
|
||||
double error1, double error2) {
|
||||
// Constructor signature must match Java! I = int, F = float, [D = double
|
||||
// array
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(detectionClass, "<init>", "(IIF[DDD[D[D[DD[D[DD)V");
|
||||
|
||||
if (!constructor) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!detect) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We have to copy the homography matrix and coners into jdoubles
|
||||
jdouble h[9]; // = new jdouble[9]{};
|
||||
for (int i = 0; i < 9; i++) {
|
||||
h[i] = detect->H->data[i];
|
||||
}
|
||||
|
||||
jdouble corners[8]; // = new jdouble[8]{};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
corners[i * 2] = detect->p[i][0];
|
||||
corners[i * 2 + 1] = detect->p[i][1];
|
||||
}
|
||||
|
||||
jdoubleArray harr = MakeJDoubleArray(env, {h, 9});
|
||||
jdoubleArray carr = MakeJDoubleArray(env, {corners, 8});
|
||||
|
||||
// The rotation of the target is encoded as a 3 by 3 rotation matrix, we'll
|
||||
// convert to a row-major array
|
||||
jdouble pose1RotMat[9] = {0};
|
||||
jdouble pose2RotMat[9] = {0};
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
if (pose1.R) {
|
||||
pose1RotMat[i] = pose1.R->data[i];
|
||||
}
|
||||
if (pose2.R) {
|
||||
pose2RotMat[i] = pose2.R->data[i];
|
||||
}
|
||||
}
|
||||
|
||||
// And translation a 3x1 vector (todo check axis order)
|
||||
jdouble pose1Trans[3] = {0};
|
||||
jdouble pose2Trans[3] = {0};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (pose1.t) {
|
||||
pose1Trans[i] = pose1.t->data[i];
|
||||
}
|
||||
if (pose2.t) {
|
||||
pose2Trans[i] = pose2.t->data[i];
|
||||
}
|
||||
}
|
||||
|
||||
jdoubleArray pose1rotArr = MakeJDoubleArray(env, {pose1RotMat, 9});
|
||||
jdoubleArray pose2rotArr = MakeJDoubleArray(env, {pose2RotMat, 9});
|
||||
jdoubleArray pose1transArr = MakeJDoubleArray(env, {pose1Trans, 3});
|
||||
jdoubleArray pose2transArr = MakeJDoubleArray(env, {pose2Trans, 3});
|
||||
jdouble err1 = error1;
|
||||
jdouble err2 = error2;
|
||||
|
||||
// Actually call the constructor
|
||||
auto ret = env->NewObject(
|
||||
detectionClass, constructor, (jint)detect->id, (jint)detect->hamming,
|
||||
(jfloat)detect->decision_margin, harr, (jdouble)detect->c[0],
|
||||
(jdouble)detect->c[1], carr, pose1transArr, pose1rotArr, err1,
|
||||
pose2transArr, pose2rotArr, err2);
|
||||
|
||||
// TODO we don't seem to need this... or at least, it doesnt leak rn
|
||||
// env->ReleaseDoubleArrayElements(harr, h, 0);
|
||||
// env->ReleaseDoubleArrayElements(carr, corners, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
|
||||
* Method: aprilTagDetectInternal
|
||||
* Signature: (JJIIZDDDDDI)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagDetectInternal
|
||||
(JNIEnv* env, jclass cls, jlong detectIdx, jlong pData, jint rows, jint cols,
|
||||
jboolean doPoseEstimation, jdouble tagWidthMeters, jdouble fx, jdouble fy,
|
||||
jdouble cx, jdouble cy, jint nIters)
|
||||
{
|
||||
// No image, can't do anything
|
||||
if (!pData) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make an image_u8_t header for the Mat data
|
||||
image_u8_t im = {static_cast<int32_t>(cols), static_cast<int32_t>(rows),
|
||||
static_cast<int32_t>(cols),
|
||||
reinterpret_cast<uint8_t*>(pData)};
|
||||
|
||||
// Get our detector
|
||||
auto state =
|
||||
std::find_if(detectors.begin(), detectors.end(),
|
||||
[&](DetectorState& s) { return s.id == detectIdx; });
|
||||
if (state == detectors.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// And run the detector on our new image
|
||||
zarray_t* detections = apriltag_detector_detect(state->td, &im);
|
||||
|
||||
int size = zarray_size(detections);
|
||||
|
||||
// Object array to return to Java
|
||||
jobjectArray jarr = env->NewObjectArray(size, detectionClass, nullptr);
|
||||
if (!jarr) {
|
||||
std::printf("Couldn't make array\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Global pose
|
||||
apriltag_pose_t pose1;
|
||||
std::memset(&pose1, 0, sizeof(pose1));
|
||||
|
||||
apriltag_pose_t pose2;
|
||||
std::memset(&pose2, 0, sizeof(pose2));
|
||||
|
||||
// std::printf("Created array %llu! Got %i targets!\n", &jarr, size);
|
||||
// Add our detected targets to the array
|
||||
for (int i = 0; i < size; ++i) {
|
||||
apriltag_detection_t* det = nullptr;
|
||||
zarray_get(detections, i, &det);
|
||||
|
||||
if (det != nullptr) {
|
||||
double err1 =
|
||||
HUGE_VAL; // Should get overwritten if pose estimation is happening
|
||||
double err2 = HUGE_VAL;
|
||||
if (doPoseEstimation) {
|
||||
// Feed results to the pose estimator
|
||||
apriltag_detection_info_t info{det, tagWidthMeters, fx, fy, cx, cy};
|
||||
estimate_tag_pose_orthogonal_iteration(&info, &err1, &pose1, &err2,
|
||||
&pose2, nIters);
|
||||
}
|
||||
|
||||
jobject obj = MakeJObject(env, det, pose1, pose2, err1, err2);
|
||||
|
||||
env->SetObjectArrayElement(jarr, i, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that stuff's in our Java-side array, we can clean up native memory
|
||||
apriltag_detections_destroy(detections);
|
||||
|
||||
return jarr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
|
||||
* Method: aprilTagDestroy
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagDestroy
|
||||
(JNIEnv* env, jclass clazz, jlong detectIdx)
|
||||
{
|
||||
auto state =
|
||||
std::find_if(detectors.begin(), detectors.end(),
|
||||
[&](DetectorState& s) { return s.id == detectIdx; });
|
||||
|
||||
if (state == detectors.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->td) {
|
||||
apriltag_detector_destroy(state->td);
|
||||
state->td = nullptr;
|
||||
}
|
||||
if (state->tf) {
|
||||
state->tf_destroy(state->tf);
|
||||
state->tf = nullptr;
|
||||
}
|
||||
|
||||
detectors.erase(detectors.begin() + detectIdx);
|
||||
}
|
||||
} // extern "C"
|
||||
@@ -21,19 +21,8 @@ struct WPILIB_DLLEXPORT AprilTag {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
bool operator==(const AprilTag&) const = default;
|
||||
};
|
||||
|
||||
WPILIB_DLLEXPORT
|
||||
|
||||
@@ -103,19 +103,8 @@ class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
|
||||
/*
|
||||
* 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;
|
||||
bool operator==(const AprilTagFieldLayout&) const = default;
|
||||
|
||||
private:
|
||||
std::unordered_map<int, AprilTag> m_apriltags;
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
}
|
||||
} ],
|
||||
"field" : {
|
||||
"length" : 8.2296,
|
||||
"width" : 16.4592
|
||||
"length" : 16.4592,
|
||||
"width" : 8.2296
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ stages:
|
||||
|
||||
- stage: TestBench
|
||||
displayName: Test Bench
|
||||
condition: false
|
||||
jobs:
|
||||
- job: Cpp
|
||||
displayName: C++
|
||||
|
||||
@@ -13,7 +13,7 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id 'base'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.0'
|
||||
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.1'
|
||||
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
|
||||
id 'edu.wpi.first.NativeUtils' apply false
|
||||
id 'edu.wpi.first.GradleJni' version '1.1.0'
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
repositories {
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
mavenLocal()
|
||||
maven {
|
||||
url = 'https://frcmaven.wpi.edu/artifactory/ex-gradle'
|
||||
}
|
||||
mavenCentral()
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2023.8.0"
|
||||
implementation "edu.wpi.first:native-utils:2023.9.0"
|
||||
}
|
||||
|
||||
@@ -61,8 +61,13 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
if (it.component.name == "${nativeName}JNI") {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
} else {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,6 +181,8 @@ nativeUtils.exportsConfigs {
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/imgui.gradle"
|
||||
|
||||
model {
|
||||
components {
|
||||
examplesMap.each { key, value ->
|
||||
@@ -188,7 +195,7 @@ model {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
lib library: 'cscore', linkage: 'shared'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
|
||||
it.buildable = false
|
||||
return
|
||||
|
||||
@@ -13,7 +13,9 @@ public class VideoMode {
|
||||
kYUYV(2),
|
||||
kRGB565(3),
|
||||
kBGR(4),
|
||||
kGray(5);
|
||||
kGray(5),
|
||||
kY16(6),
|
||||
kUYVY(7);
|
||||
|
||||
private final int value;
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
// Color convert
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kRGB565:
|
||||
// If source is YUYV or Gray, need to convert to BGR first
|
||||
// If source is YUYV, UYVY, Gray, or Y16, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
@@ -226,6 +226,14 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
} else {
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
}
|
||||
} else if (cur->pixelFormat == VideoMode::kUYVY) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
|
||||
cur = newImage;
|
||||
} else {
|
||||
cur = ConvertUYVYToBGR(cur);
|
||||
}
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
@@ -234,18 +242,35 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
} else {
|
||||
cur = ConvertGrayToBGR(cur);
|
||||
}
|
||||
}
|
||||
return ConvertBGRToRGB565(cur);
|
||||
case VideoMode::kGray:
|
||||
// If source is YUYV or RGB565, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
} else if (cur->pixelFormat == VideoMode::kY16) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
|
||||
cur = newImage;
|
||||
} else if (Image* newImage = GetExistingImage(cur->width, cur->height,
|
||||
VideoMode::kGray)) {
|
||||
cur = ConvertGrayToBGR(newImage);
|
||||
} else {
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
cur = ConvertGrayToBGR(ConvertY16ToGray(cur));
|
||||
}
|
||||
}
|
||||
return ConvertBGRToRGB565(cur);
|
||||
case VideoMode::kGray:
|
||||
case VideoMode::kY16:
|
||||
// If source is also grayscale, convert directly
|
||||
if (pixelFormat == VideoMode::kGray &&
|
||||
cur->pixelFormat == VideoMode::kY16) {
|
||||
return ConvertY16ToGray(cur);
|
||||
} else if (pixelFormat == VideoMode::kY16 &&
|
||||
cur->pixelFormat == VideoMode::kGray) {
|
||||
return ConvertGrayToY16(cur);
|
||||
}
|
||||
// If source is YUYV, UYVY, convert directly to Gray
|
||||
// If RGB565, need to convert to BGR first
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
cur = ConvertYUYVToGray(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kUYVY) {
|
||||
cur = ConvertUYVYToGray(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
// Check to see if BGR version already exists...
|
||||
if (Image* newImage =
|
||||
@@ -254,12 +279,18 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
} else {
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
}
|
||||
cur = ConvertBGRToGray(cur);
|
||||
}
|
||||
return ConvertBGRToGray(cur);
|
||||
if (pixelFormat == VideoMode::kY16) {
|
||||
cur = ConvertGrayToY16(cur);
|
||||
}
|
||||
return cur;
|
||||
case VideoMode::kBGR:
|
||||
case VideoMode::kMJPEG:
|
||||
if (cur->pixelFormat == VideoMode::kYUYV) {
|
||||
cur = ConvertYUYVToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kUYVY) {
|
||||
cur = ConvertUYVYToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kRGB565) {
|
||||
cur = ConvertRGB565ToBGR(cur);
|
||||
} else if (cur->pixelFormat == VideoMode::kGray) {
|
||||
@@ -268,9 +299,23 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
|
||||
} else {
|
||||
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
|
||||
}
|
||||
} else if (cur->pixelFormat == VideoMode::kY16) {
|
||||
// Check to see if Gray version already exists...
|
||||
if (Image* newImage =
|
||||
GetExistingImage(cur->width, cur->height, VideoMode::kGray)) {
|
||||
cur = newImage;
|
||||
} else {
|
||||
cur = ConvertY16ToGray(cur);
|
||||
}
|
||||
if (pixelFormat == VideoMode::kBGR) {
|
||||
return ConvertGrayToBGR(cur);
|
||||
} else {
|
||||
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kUYVY:
|
||||
default:
|
||||
return nullptr; // Unsupported
|
||||
}
|
||||
@@ -351,6 +396,72 @@ Image* Frame::ConvertYUYVToBGR(Image* image) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertYUYVToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kYUYV) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_YUYV);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertUYVYToBGR(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kUYVY) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a BGR image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
|
||||
image->width * image->height * 3);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_UYVY);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertUYVYToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kUYVY) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Convert
|
||||
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_UYVY);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertBGRToRGB565(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kBGR) {
|
||||
return nullptr;
|
||||
@@ -509,6 +620,50 @@ Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertGrayToY16(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kGray) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a Y16 image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kY16, image->width, image->height,
|
||||
image->width * image->height * 2);
|
||||
|
||||
// Convert with linear scaling
|
||||
image->AsMat().convertTo(newImage->AsMat(), CV_16U, 256);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::ConvertY16ToGray(Image* image) {
|
||||
if (!image || image->pixelFormat != VideoMode::kY16) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Allocate a Grayscale image
|
||||
auto newImage =
|
||||
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
|
||||
image->width * image->height);
|
||||
|
||||
// Scale min to 0 and max to 255
|
||||
cv::normalize(image->AsMat(), newImage->AsMat(), 255, 0, cv::NORM_MINMAX);
|
||||
|
||||
// Save the result
|
||||
Image* rv = newImage.release();
|
||||
if (m_impl) {
|
||||
std::scoped_lock lock(m_impl->mutex);
|
||||
m_impl->images.push_back(rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
Image* Frame::GetImageImpl(int width, int height,
|
||||
VideoMode::PixelFormat pixelFormat,
|
||||
int requiredJpegQuality, int defaultJpegQuality) {
|
||||
|
||||
@@ -195,12 +195,17 @@ class Frame {
|
||||
Image* ConvertMJPEGToBGR(Image* image);
|
||||
Image* ConvertMJPEGToGray(Image* image);
|
||||
Image* ConvertYUYVToBGR(Image* image);
|
||||
Image* ConvertYUYVToGray(Image* image);
|
||||
Image* ConvertUYVYToBGR(Image* image);
|
||||
Image* ConvertUYVYToGray(Image* image);
|
||||
Image* ConvertBGRToRGB565(Image* image);
|
||||
Image* ConvertRGB565ToBGR(Image* image);
|
||||
Image* ConvertBGRToGray(Image* image);
|
||||
Image* ConvertGrayToBGR(Image* image);
|
||||
Image* ConvertBGRToMJPEG(Image* image, int quality);
|
||||
Image* ConvertGrayToMJPEG(Image* image, int quality);
|
||||
Image* ConvertGrayToY16(Image* image);
|
||||
Image* ConvertY16ToGray(Image* image);
|
||||
|
||||
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat) {
|
||||
if (pixelFormat == VideoMode::kMJPEG) {
|
||||
|
||||
@@ -74,6 +74,8 @@ class Image {
|
||||
switch (pixelFormat) {
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
case VideoMode::kY16:
|
||||
case VideoMode::kUYVY:
|
||||
type = CV_8UC2;
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
|
||||
@@ -460,6 +460,12 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
|
||||
case VideoMode::kGray:
|
||||
os << "gray";
|
||||
break;
|
||||
case VideoMode::kY16:
|
||||
os << "Y16";
|
||||
break;
|
||||
case VideoMode::kUYVY:
|
||||
os << "UYVY";
|
||||
break;
|
||||
default:
|
||||
os << "unknown";
|
||||
break;
|
||||
@@ -569,6 +575,12 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
|
||||
case VideoMode::kGray:
|
||||
os << "gray";
|
||||
break;
|
||||
case VideoMode::kY16:
|
||||
os << "Y16";
|
||||
break;
|
||||
case VideoMode::kUYVY:
|
||||
os << "UYVY";
|
||||
break;
|
||||
default:
|
||||
os << "unknown";
|
||||
break;
|
||||
@@ -740,8 +752,10 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
// for adding it if required.
|
||||
addDHT = JpegNeedsDHT(data, &size, &locSOF);
|
||||
break;
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kUYVY:
|
||||
case VideoMode::kRGB565:
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kY16:
|
||||
default:
|
||||
// Bad frame; sleep for 10 ms so we don't consume all processor time.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
@@ -26,6 +26,8 @@ void RawSourceImpl::PutFrame(const CS_RawFrame& image) {
|
||||
switch (image.pixelFormat) {
|
||||
case VideoMode::kYUYV:
|
||||
case VideoMode::kRGB565:
|
||||
case VideoMode::kY16:
|
||||
case VideoMode::kUYVY:
|
||||
type = CV_8UC2;
|
||||
break;
|
||||
case VideoMode::kBGR:
|
||||
|
||||
@@ -198,6 +198,10 @@ bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
|
||||
mode.pixelFormat = cs::VideoMode::kBGR;
|
||||
} else if (wpi::equals_lower(str, "gray")) {
|
||||
mode.pixelFormat = cs::VideoMode::kGray;
|
||||
} else if (wpi::equals_lower(str, "y16")) {
|
||||
mode.pixelFormat = cs::VideoMode::kY16;
|
||||
} else if (wpi::equals_lower(str, "uyvy")) {
|
||||
mode.pixelFormat = cs::VideoMode::kUYVY;
|
||||
} else {
|
||||
SWARNING("SetConfigJson: could not understand pixel format value '{}'",
|
||||
str);
|
||||
@@ -360,6 +364,12 @@ wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
|
||||
case VideoMode::kGray:
|
||||
pixelFormat = "gray";
|
||||
break;
|
||||
case VideoMode::kY16:
|
||||
pixelFormat = "y16";
|
||||
break;
|
||||
case VideoMode::kUYVY:
|
||||
pixelFormat = "uyvy";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,9 @@ enum CS_PixelFormat {
|
||||
CS_PIXFMT_YUYV,
|
||||
CS_PIXFMT_RGB565,
|
||||
CS_PIXFMT_BGR,
|
||||
CS_PIXFMT_GRAY
|
||||
CS_PIXFMT_GRAY,
|
||||
CS_PIXFMT_Y16,
|
||||
CS_PIXFMT_UYVY
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,7 +68,9 @@ struct VideoMode : public CS_VideoMode {
|
||||
kYUYV = CS_PIXFMT_YUYV,
|
||||
kRGB565 = CS_PIXFMT_RGB565,
|
||||
kBGR = CS_PIXFMT_BGR,
|
||||
kGray = CS_PIXFMT_GRAY
|
||||
kGray = CS_PIXFMT_GRAY,
|
||||
kY16 = CS_PIXFMT_Y16,
|
||||
kUYVY = CS_PIXFMT_UYVY
|
||||
};
|
||||
VideoMode() {
|
||||
pixelFormat = 0;
|
||||
@@ -88,8 +90,6 @@ struct VideoMode : public CS_VideoMode {
|
||||
return pixelFormat == other.pixelFormat && width == other.width &&
|
||||
height == other.height && fps == other.fps;
|
||||
}
|
||||
|
||||
bool operator!=(const VideoMode& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -140,8 +140,6 @@ class VideoSource {
|
||||
return m_handle == other.m_handle;
|
||||
}
|
||||
|
||||
bool operator!=(const VideoSource& other) const { return !(*this == other); }
|
||||
|
||||
/**
|
||||
* Get the kind of the source.
|
||||
*/
|
||||
@@ -736,8 +734,6 @@ class VideoSink {
|
||||
return m_handle == other.m_handle;
|
||||
}
|
||||
|
||||
bool operator!=(const VideoSink& other) const { return !(*this == other); }
|
||||
|
||||
/**
|
||||
* Get the kind of the sink.
|
||||
*/
|
||||
|
||||
@@ -82,6 +82,10 @@ static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) {
|
||||
return VideoMode::kBGR;
|
||||
case V4L2_PIX_FMT_GREY:
|
||||
return VideoMode::kGray;
|
||||
case V4L2_PIX_FMT_Y16:
|
||||
return VideoMode::kY16;
|
||||
case V4L2_PIX_FMT_UYVY:
|
||||
return VideoMode::kUYVY;
|
||||
default:
|
||||
return VideoMode::kUnknown;
|
||||
}
|
||||
@@ -100,6 +104,10 @@ static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
|
||||
return V4L2_PIX_FMT_BGR24;
|
||||
case VideoMode::kGray:
|
||||
return V4L2_PIX_FMT_GREY;
|
||||
case VideoMode::kY16:
|
||||
return V4L2_PIX_FMT_Y16;
|
||||
case VideoMode::kUYVY:
|
||||
return V4L2_PIX_FMT_UYVY;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 "Instance.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -9,12 +10,16 @@ namespace cs {
|
||||
CS_Source CreateUsbCameraDev(std::string_view name, int dev,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
WPI_ERROR(Instance::GetInstance().logger,
|
||||
"USB Camera support not implemented for macOS");
|
||||
return 0;
|
||||
}
|
||||
|
||||
CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
|
||||
CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
WPI_ERROR(Instance::GetInstance().logger,
|
||||
"USB Camera support not implemented for macOS");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,6 +40,8 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
|
||||
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
WPI_ERROR(Instance::GetInstance().logger,
|
||||
"USB Camera support not implemented for macOS");
|
||||
return std::vector<UsbCameraInfo>{};
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,12 @@ void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
|
||||
tmpMat.total());
|
||||
tmpMat.copyTo(dest->AsMat());
|
||||
break;
|
||||
case cs::VideoMode::PixelFormat::kY16:
|
||||
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC2, ptr, pitch);
|
||||
dest =
|
||||
AllocImage(VideoMode::kY16, tmpMat.cols, tmpMat.rows, tmpMat.total());
|
||||
tmpMat.copyTo(dest->AsMat());
|
||||
break;
|
||||
case cs::VideoMode::PixelFormat::kBGR:
|
||||
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC3, ptr, pitch);
|
||||
dest = AllocImage(VideoMode::kBGR, tmpMat.cols, tmpMat.rows,
|
||||
@@ -368,6 +374,12 @@ void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
|
||||
tmpMat.total() * 2);
|
||||
tmpMat.copyTo(dest->AsMat());
|
||||
break;
|
||||
case cs::VideoMode::PixelFormat::kUYVY:
|
||||
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC2, ptr, pitch);
|
||||
dest = AllocImage(VideoMode::kUYVY, tmpMat.cols, tmpMat.rows,
|
||||
tmpMat.total() * 2);
|
||||
tmpMat.copyTo(dest->AsMat());
|
||||
break;
|
||||
default:
|
||||
doFinalSet = false;
|
||||
break;
|
||||
@@ -461,9 +473,10 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
|
||||
|
||||
static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
|
||||
// Compare GUID to one of the supported ones
|
||||
if (IsEqualGUID(guid, MFVideoFormat_NV12)) {
|
||||
// GrayScale
|
||||
if (IsEqualGUID(guid, MFVideoFormat_L8)) {
|
||||
return cs::VideoMode::PixelFormat::kGray;
|
||||
} else if (IsEqualGUID(guid, MFVideoFormat_L16)) {
|
||||
return cs::VideoMode::PixelFormat::kY16;
|
||||
} else if (IsEqualGUID(guid, MFVideoFormat_YUY2)) {
|
||||
return cs::VideoMode::PixelFormat::kYUYV;
|
||||
} else if (IsEqualGUID(guid, MFVideoFormat_RGB24)) {
|
||||
@@ -472,6 +485,8 @@ static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
|
||||
return cs::VideoMode::PixelFormat::kMJPEG;
|
||||
} else if (IsEqualGUID(guid, MFVideoFormat_RGB565)) {
|
||||
return cs::VideoMode::PixelFormat::kRGB565;
|
||||
} else if (IsEqualGUID(guid, MFVideoFormat_UYVY)) {
|
||||
return cs::VideoMode::PixelFormat::kUYVY;
|
||||
} else {
|
||||
return cs::VideoMode::PixelFormat::kUnknown;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
|
||||
|
||||
apply from: "${rootDir}/shared/libssh.gradle"
|
||||
apply from: "${rootDir}/shared/imgui.gradle"
|
||||
|
||||
task generateCppVersion() {
|
||||
description = 'Generates the wpilib version class'
|
||||
@@ -103,7 +104,7 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
lib project: ':glass', library: 'glass', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static', 'libssh')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui', 'libssh')
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
it.linker.args << 'ws2_32.lib' << 'advapi32.lib' << 'crypt32.lib' << 'user32.lib'
|
||||
|
||||
@@ -3,16 +3,17 @@ plugins {
|
||||
id "org.ysb33r.doxygen" version "0.7.0"
|
||||
}
|
||||
|
||||
evaluationDependsOn(':wpiutil')
|
||||
evaluationDependsOn(':wpinet')
|
||||
evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':apriltag')
|
||||
evaluationDependsOn(':cameraserver')
|
||||
evaluationDependsOn(':cscore')
|
||||
evaluationDependsOn(':hal')
|
||||
evaluationDependsOn(':cameraserver')
|
||||
evaluationDependsOn(':wpimath')
|
||||
evaluationDependsOn(':ntcore')
|
||||
evaluationDependsOn(':wpilibNewCommands')
|
||||
evaluationDependsOn(':wpilibc')
|
||||
evaluationDependsOn(':wpilibj')
|
||||
evaluationDependsOn(':wpilibNewCommands')
|
||||
evaluationDependsOn(':wpimath')
|
||||
evaluationDependsOn(':wpinet')
|
||||
evaluationDependsOn(':wpiutil')
|
||||
|
||||
def baseArtifactIdCpp = 'documentation'
|
||||
def artifactGroupIdCpp = 'edu.wpi.first.wpilibc'
|
||||
@@ -27,15 +28,16 @@ def outputsFolder = file("$project.buildDir/outputs")
|
||||
def cppProjectZips = []
|
||||
def cppIncludeRoots = []
|
||||
|
||||
cppProjectZips.add(project(':hal').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpinet').cppHeadersZip)
|
||||
cppProjectZips.add(project(':ntcore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cscore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':apriltag').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpimath').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
|
||||
cppProjectZips.add(project(':cscore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':hal').cppHeadersZip)
|
||||
cppProjectZips.add(project(':ntcore').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibNewCommands').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpimath').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpinet').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
|
||||
doxygen {
|
||||
executables {
|
||||
@@ -161,7 +163,7 @@ doxygen {
|
||||
warn_if_undocumented false
|
||||
warn_no_paramdoc true
|
||||
|
||||
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix SpeedController docs
|
||||
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix MotorController docs
|
||||
enable_preprocessing true
|
||||
macro_expansion true
|
||||
expand_only_predef true
|
||||
@@ -205,19 +207,20 @@ task generateJavaDocs(type: Javadoc) {
|
||||
options.addBooleanOption("Xdoclint:html,missing,reference,syntax", true)
|
||||
options.addBooleanOption('html5', true)
|
||||
options.linkSource(true)
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':hal').generateUsageReporting
|
||||
dependsOn project(':wpimath').generateNat
|
||||
dependsOn project(':ntcore').ntcoreGenerateJavaTypes
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source project(':wpinet').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
source project(':ntcore').sourceSets.main.java
|
||||
source project(':wpimath').sourceSets.main.java
|
||||
source project(':wpilibj').sourceSets.main.java
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':wpimath').generateNat
|
||||
source project(':apriltag').sourceSets.main.java
|
||||
source project(':cameraserver').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':ntcore').sourceSets.main.java
|
||||
source project(':wpilibNewCommands').sourceSets.main.java
|
||||
source project(':wpilibj').sourceSets.main.java
|
||||
source project(':wpimath').sourceSets.main.java
|
||||
source project(':wpinet').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source configurations.javaSource.collect { zipTree(it) }
|
||||
include '**/*.java'
|
||||
failOnError = true
|
||||
|
||||
@@ -24,6 +24,8 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
def wpilibVersionFileInput = file("src/app/generate/WPILibVersion.cpp.in")
|
||||
def wpilibVersionFileOutput = file("$buildDir/generated/app/cpp/WPILibVersion.cpp")
|
||||
|
||||
apply from: "${rootDir}/shared/imgui.gradle"
|
||||
|
||||
task generateCppVersion() {
|
||||
description = 'Generates the wpilib version class'
|
||||
group = 'WPILib'
|
||||
@@ -111,7 +113,7 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui')
|
||||
}
|
||||
appendDebugPathToBinaries(binaries)
|
||||
}
|
||||
@@ -142,7 +144,7 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui')
|
||||
}
|
||||
appendDebugPathToBinaries(binaries)
|
||||
}
|
||||
@@ -182,7 +184,7 @@ if (!project.hasProperty('onlylinuxathena')) {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'opencv_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui')
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
it.linker.args << '/DELAYLOAD:MF.dll' << '/DELAYLOAD:MFReadWrite.dll' << '/DELAYLOAD:MFPlat.dll' << '/delay:nobind'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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 "glass/hardware/SpeedController.h"
|
||||
#include "glass/hardware/MotorController.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void glass::DisplaySpeedController(SpeedControllerModel* m) {
|
||||
void glass::DisplayMotorController(MotorControllerModel* m) {
|
||||
// Get duty cycle data from the model and do not display anything if the data
|
||||
// is null.
|
||||
auto dc = m->GetPercentData();
|
||||
if (!dc || !m->Exists()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(96, 96, 96, 255));
|
||||
ImGui::Text("Unknown SpeedController");
|
||||
ImGui::Text("Unknown MotorController");
|
||||
ImGui::PopStyleColor();
|
||||
return;
|
||||
}
|
||||
@@ -991,7 +991,7 @@ void PlotProvider::DisplayMenu() {
|
||||
for (size_t i = 0; i <= numWindows; ++i) {
|
||||
std::snprintf(id, sizeof(id), "Plot <%d>", static_cast<int>(i));
|
||||
bool match = false;
|
||||
for (size_t j = i; j < numWindows; ++j) {
|
||||
for (size_t j = 0; j < numWindows; ++j) {
|
||||
if (m_windows[j]->GetId() == id) {
|
||||
match = true;
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/spinlock.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -37,12 +38,21 @@ class DataSource {
|
||||
bool IsDigital() const { return m_digital; }
|
||||
|
||||
void SetValue(double value, int64_t time = 0) {
|
||||
std::scoped_lock lock{m_valueMutex};
|
||||
m_value = value;
|
||||
m_valueTime = time;
|
||||
valueChanged(value, time);
|
||||
}
|
||||
double GetValue() const { return m_value; }
|
||||
int64_t GetValueTime() const { return m_valueTime; }
|
||||
|
||||
double GetValue() const {
|
||||
std::scoped_lock lock{m_valueMutex};
|
||||
return m_value;
|
||||
}
|
||||
|
||||
int64_t GetValueTime() const {
|
||||
std::scoped_lock lock{m_valueMutex};
|
||||
return m_valueTime;
|
||||
}
|
||||
|
||||
// drag source helpers
|
||||
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
|
||||
@@ -59,7 +69,7 @@ class DataSource {
|
||||
ImGuiInputTextFlags flags = 0) const;
|
||||
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
|
||||
|
||||
wpi::sig::Signal<double, int64_t> valueChanged;
|
||||
wpi::sig::SignalBase<wpi::spinlock, double, int64_t> valueChanged;
|
||||
|
||||
static DataSource* Find(std::string_view id);
|
||||
|
||||
@@ -69,6 +79,7 @@ class DataSource {
|
||||
std::string m_id;
|
||||
std::string& m_name;
|
||||
bool m_digital = false;
|
||||
mutable wpi::spinlock m_valueMutex;
|
||||
double m_value = 0;
|
||||
int64_t m_valueTime = 0;
|
||||
};
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
namespace glass {
|
||||
class DataSource;
|
||||
class SpeedControllerModel : public Model {
|
||||
class MotorControllerModel : public Model {
|
||||
public:
|
||||
virtual const char* GetName() const = 0;
|
||||
virtual const char* GetSimDevice() const = 0;
|
||||
virtual DataSource* GetPercentData() = 0;
|
||||
virtual void SetPercent(double value) = 0;
|
||||
};
|
||||
void DisplaySpeedController(SpeedControllerModel* m);
|
||||
void DisplayMotorController(MotorControllerModel* m);
|
||||
} // namespace glass
|
||||
@@ -43,5 +43,5 @@ void NTCommandSchedulerModel::Update() {
|
||||
}
|
||||
|
||||
bool NTCommandSchedulerModel::Exists() {
|
||||
return m_inst.IsConnected() && m_commands.Exists();
|
||||
return m_commands.Exists();
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@ void NTCommandSelectorModel::Update() {
|
||||
}
|
||||
|
||||
bool NTCommandSelectorModel::Exists() {
|
||||
return m_inst.IsConnected() && m_running.Exists();
|
||||
return m_running.Exists();
|
||||
}
|
||||
|
||||
@@ -56,5 +56,5 @@ void NTDifferentialDriveModel::Update() {
|
||||
}
|
||||
|
||||
bool NTDifferentialDriveModel::Exists() {
|
||||
return m_inst.IsConnected() && m_lPercent.Exists();
|
||||
return m_lPercent.Exists();
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@ void NTDigitalInputModel::Update() {
|
||||
}
|
||||
|
||||
bool NTDigitalInputModel::Exists() {
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
return m_value.Exists();
|
||||
}
|
||||
|
||||
@@ -40,5 +40,5 @@ void NTDigitalOutputModel::Update() {
|
||||
}
|
||||
|
||||
bool NTDigitalOutputModel::Exists() {
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
return m_value.Exists();
|
||||
}
|
||||
|
||||
@@ -73,5 +73,5 @@ void NTFMSModel::Update() {
|
||||
}
|
||||
|
||||
bool NTFMSModel::Exists() {
|
||||
return m_inst.IsConnected() && m_controlWord.Exists();
|
||||
return m_controlWord.Exists();
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ void NTField2DModel::Update() {
|
||||
}
|
||||
|
||||
bool NTField2DModel::Exists() {
|
||||
return m_inst.IsConnected() && m_nameTopic.Exists();
|
||||
return m_nameTopic.Exists();
|
||||
}
|
||||
|
||||
bool NTField2DModel::IsReadOnly() {
|
||||
|
||||
@@ -30,5 +30,5 @@ void NTGyroModel::Update() {
|
||||
}
|
||||
|
||||
bool NTGyroModel::Exists() {
|
||||
return m_inst.IsConnected() && m_angle.Exists();
|
||||
return m_angle.Exists();
|
||||
}
|
||||
|
||||
@@ -81,5 +81,5 @@ void NTMecanumDriveModel::Update() {
|
||||
}
|
||||
|
||||
bool NTMecanumDriveModel::Exists() {
|
||||
return m_inst.IsConnected() && m_flPercent.Exists();
|
||||
return m_flPercent.Exists();
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ void NTMechanism2DModel::Update() {
|
||||
}
|
||||
|
||||
bool NTMechanism2DModel::Exists() {
|
||||
return m_inst.IsConnected() && m_nameTopic.Exists();
|
||||
return m_nameTopic.Exists();
|
||||
}
|
||||
|
||||
bool NTMechanism2DModel::IsReadOnly() {
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
// 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 "glass/networktables/NTSpeedController.h"
|
||||
#include "glass/networktables/NTMotorController.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
NTSpeedControllerModel::NTSpeedControllerModel(std::string_view path)
|
||||
: NTSpeedControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
NTMotorControllerModel::NTMotorControllerModel(std::string_view path)
|
||||
: NTMotorControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTSpeedControllerModel::NTSpeedControllerModel(nt::NetworkTableInstance inst,
|
||||
NTMotorControllerModel::NTMotorControllerModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_value{inst.GetDoubleTopic(fmt::format("{}/Value", path))
|
||||
@@ -23,11 +23,11 @@ NTSpeedControllerModel::NTSpeedControllerModel(nt::NetworkTableInstance inst,
|
||||
m_valueData{fmt::format("NT_SpdCtrl:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTSpeedControllerModel::SetPercent(double value) {
|
||||
void NTMotorControllerModel::SetPercent(double value) {
|
||||
m_value.Set(value);
|
||||
}
|
||||
|
||||
void NTSpeedControllerModel::Update() {
|
||||
void NTMotorControllerModel::Update() {
|
||||
for (auto&& v : m_value.ReadQueue()) {
|
||||
m_valueData.SetValue(v.value, v.time);
|
||||
}
|
||||
@@ -39,6 +39,6 @@ void NTSpeedControllerModel::Update() {
|
||||
}
|
||||
}
|
||||
|
||||
bool NTSpeedControllerModel::Exists() {
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
bool NTMotorControllerModel::Exists() {
|
||||
return m_value.Exists();
|
||||
}
|
||||
@@ -70,5 +70,5 @@ void NTPIDControllerModel::Update() {
|
||||
}
|
||||
|
||||
bool NTPIDControllerModel::Exists() {
|
||||
return m_inst.IsConnected() && m_setpoint.Exists();
|
||||
return m_setpoint.Exists();
|
||||
}
|
||||
|
||||
@@ -60,5 +60,5 @@ void NTStringChooserModel::Update() {
|
||||
}
|
||||
|
||||
bool NTStringChooserModel::Exists() {
|
||||
return m_inst.IsConnected() && m_options.Exists();
|
||||
return m_options.Exists();
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@ void NTSubsystemModel::Update() {
|
||||
}
|
||||
|
||||
bool NTSubsystemModel::Exists() {
|
||||
return m_inst.IsConnected() && m_defaultCommand.Exists();
|
||||
return m_defaultCommand.Exists();
|
||||
}
|
||||
|
||||
@@ -432,6 +432,27 @@ void NetworkTablesModel::Update() {
|
||||
}
|
||||
}
|
||||
if (event.flags & nt::EventFlags::kUnpublish) {
|
||||
// meta topic handling
|
||||
if (wpi::starts_with(info->name, '$')) {
|
||||
// meta topic handling
|
||||
if (info->name == "$clients") {
|
||||
m_clients.clear();
|
||||
} else if (info->name == "$serverpub") {
|
||||
m_server.publishers.clear();
|
||||
} else if (info->name == "$serversub") {
|
||||
m_server.subscribers.clear();
|
||||
} else if (wpi::starts_with(info->name, "$clientpub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(info->name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.publishers.clear();
|
||||
}
|
||||
} else if (wpi::starts_with(info->name, "$clientsub$")) {
|
||||
auto it = m_clients.find(wpi::drop_front(info->name, 11));
|
||||
if (it != m_clients.end()) {
|
||||
it->second.subscribers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
|
||||
entry.get());
|
||||
// will be removed completely below
|
||||
@@ -552,7 +573,7 @@ void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
|
||||
}
|
||||
|
||||
bool NetworkTablesModel::Exists() {
|
||||
return m_inst.IsConnected();
|
||||
return true;
|
||||
}
|
||||
|
||||
NetworkTablesModel::Entry* NetworkTablesModel::GetEntry(std::string_view name) {
|
||||
@@ -616,9 +637,9 @@ static void DecodeSubscriberOptions(
|
||||
for (uint32_t j = 0; j < numMapElem; ++j) {
|
||||
std::string key;
|
||||
mpack_expect_str(&r, &key);
|
||||
if (key == "immediate") {
|
||||
options->immediate = mpack_expect_bool(&r);
|
||||
} else if (key == "sendAll") {
|
||||
if (key == "topicsonly") {
|
||||
options->topicsOnly = mpack_expect_bool(&r);
|
||||
} else if (key == "all") {
|
||||
options->sendAll = mpack_expect_bool(&r);
|
||||
} else if (key == "periodic") {
|
||||
options->periodic = mpack_expect_float(&r);
|
||||
@@ -828,54 +849,6 @@ static bool StringToFloatArray(std::string_view in, std::vector<T>* out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int fromxdigit(char ch) {
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return (ch - 'a' + 10);
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
return (ch - 'A' + 10);
|
||||
} else {
|
||||
return ch - '0';
|
||||
}
|
||||
}
|
||||
|
||||
static std::string_view UnescapeString(std::string_view source,
|
||||
wpi::SmallVectorImpl<char>& buf) {
|
||||
assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
|
||||
buf.clear();
|
||||
buf.reserve(source.size() - 2);
|
||||
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
|
||||
if (*s != '\\') {
|
||||
buf.push_back(*s);
|
||||
continue;
|
||||
}
|
||||
switch (*++s) {
|
||||
case 't':
|
||||
buf.push_back('\t');
|
||||
break;
|
||||
case 'n':
|
||||
buf.push_back('\n');
|
||||
break;
|
||||
case 'x': {
|
||||
if (!isxdigit(*(s + 1))) {
|
||||
buf.push_back('x'); // treat it like a unknown escape
|
||||
break;
|
||||
}
|
||||
int ch = fromxdigit(*++s);
|
||||
if (std::isxdigit(*(s + 1))) {
|
||||
ch <<= 4;
|
||||
ch |= fromxdigit(*++s);
|
||||
}
|
||||
buf.push_back(static_cast<char>(ch));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
buf.push_back(*s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
static bool StringToStringArray(std::string_view in,
|
||||
std::vector<std::string>* out) {
|
||||
in = wpi::trim(in);
|
||||
@@ -904,7 +877,9 @@ static bool StringToStringArray(std::string_view in,
|
||||
"GUI: NetworkTables: Could not understand value '{}'\n", val);
|
||||
return false;
|
||||
}
|
||||
out->emplace_back(UnescapeString(val, buf));
|
||||
val.remove_prefix(1);
|
||||
val.remove_suffix(1);
|
||||
out->emplace_back(wpi::UnescapeCString(val, buf).first);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1454,7 +1429,7 @@ static void DisplayClient(const NetworkTablesModel::Client& client) {
|
||||
ImGui::TableSetupColumn("Topics", ImGuiTableColumnFlags_WidthStretch, 6.0f);
|
||||
ImGui::TableSetupColumn("Periodic", ImGuiTableColumnFlags_WidthStretch,
|
||||
1.0f);
|
||||
ImGui::TableSetupColumn("Immediate", ImGuiTableColumnFlags_WidthStretch,
|
||||
ImGui::TableSetupColumn("Topics Only", ImGuiTableColumnFlags_WidthStretch,
|
||||
1.0f);
|
||||
ImGui::TableSetupColumn("Send All", ImGuiTableColumnFlags_WidthStretch,
|
||||
1.0f);
|
||||
@@ -1470,7 +1445,7 @@ static void DisplayClient(const NetworkTablesModel::Client& client) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%0.3f", sub.options.periodic);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(sub.options.immediate ? "Yes" : "No");
|
||||
ImGui::Text(sub.options.topicsOnly ? "Yes" : "No");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(sub.options.sendAll ? "Yes" : "No");
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#include "glass/networktables/NTGyro.h"
|
||||
#include "glass/networktables/NTMecanumDrive.h"
|
||||
#include "glass/networktables/NTMechanism2D.h"
|
||||
#include "glass/networktables/NTMotorController.h"
|
||||
#include "glass/networktables/NTPIDController.h"
|
||||
#include "glass/networktables/NTSpeedController.h"
|
||||
#include "glass/networktables/NTStringChooser.h"
|
||||
#include "glass/networktables/NTSubsystem.h"
|
||||
#include "glass/networktables/NetworkTablesProvider.h"
|
||||
@@ -142,14 +142,14 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
});
|
||||
provider.Register(
|
||||
NTSpeedControllerModel::kType,
|
||||
NTMotorControllerModel::kType,
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTSpeedControllerModel>(inst, path);
|
||||
return std::make_unique<NTMotorControllerModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char* path) {
|
||||
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
return MakeFunctionView([=] {
|
||||
DisplaySpeedController(static_cast<NTSpeedControllerModel*>(model));
|
||||
DisplayMotorController(static_cast<NTMotorControllerModel*>(model));
|
||||
});
|
||||
});
|
||||
provider.Register(
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/SpeedController.h"
|
||||
#include "glass/hardware/MotorController.h"
|
||||
|
||||
namespace glass {
|
||||
class NTSpeedControllerModel : public SpeedControllerModel {
|
||||
class NTMotorControllerModel : public MotorControllerModel {
|
||||
public:
|
||||
static constexpr const char* kType = "Motor Controller";
|
||||
|
||||
explicit NTSpeedControllerModel(std::string_view path);
|
||||
NTSpeedControllerModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
explicit NTMotorControllerModel(std::string_view path);
|
||||
NTMotorControllerModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const char* GetSimDevice() const override { return nullptr; }
|
||||
@@ -30,7 +30,7 @@ class NetworkTablesModel : public Model {
|
||||
public:
|
||||
struct SubscriberOptions {
|
||||
float periodic = 0.1f;
|
||||
bool immediate = false;
|
||||
bool topicsOnly = false;
|
||||
bool sendAll = false;
|
||||
bool prefixMatch = false;
|
||||
// std::string otherStr;
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
# Download and unpack googletest at configure time
|
||||
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
|
||||
if(result)
|
||||
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
|
||||
endif()
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build .
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
|
||||
if(result)
|
||||
message(FATAL_ERROR "Build step for googletest failed: ${result}")
|
||||
endif()
|
||||
include(FetchContent)
|
||||
|
||||
# Prevent overriding the parent project's compiler/linker
|
||||
# settings on Windows
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG 58d77fa8070e8cec2dc1ed015d66b454c8d78850 # 1.12.1
|
||||
)
|
||||
|
||||
# Add googletest directly to our build. This defines
|
||||
# the gtest and gtest_main targets.
|
||||
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
|
||||
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
|
||||
EXCLUDE_FROM_ALL)
|
||||
FetchContent_GetProperties(googletest)
|
||||
if(NOT googletest_POPULATED)
|
||||
FetchContent_Populate(googletest)
|
||||
|
||||
# Prevent overriding the parent project's compiler/linker
|
||||
# settings on Windows
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
target_compile_features(gtest PUBLIC cxx_std_20)
|
||||
target_compile_features(gtest_main PUBLIC cxx_std_20)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
|
||||
project(googletest-download NONE)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG e2239ee6043f73722e7aa812a459f54a28552929 # 1.11.0
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
35
hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java
Normal file
35
hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
public class CANStreamMessage {
|
||||
@SuppressWarnings("MemberName")
|
||||
public final byte[] data = new byte[8];
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public int length;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public long timestamp;
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
public int messageID;
|
||||
|
||||
/**
|
||||
* API used from JNI to set the data.
|
||||
*
|
||||
* @param length Length of packet in bytes.
|
||||
* @param messageID CAN message ID of the message.
|
||||
* @param timestamp CAN frame timestamp in microseconds.
|
||||
* @return Buffer containing CAN frame.
|
||||
*/
|
||||
@SuppressWarnings("PMD.MethodReturnsInternalArray")
|
||||
public byte[] setStreamData(int length, int messageID, long timestamp) {
|
||||
this.messageID = messageID;
|
||||
this.length = length;
|
||||
this.timestamp = timestamp;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ public final class HALUtil extends JNIWrapper {
|
||||
|
||||
public static native int getFPGARevision();
|
||||
|
||||
public static native String getSerialNumber();
|
||||
|
||||
public static native long getFPGATime();
|
||||
|
||||
public static native int getHALRuntimeType();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package edu.wpi.first.hal.can;
|
||||
|
||||
import edu.wpi.first.hal.CANStreamMessage;
|
||||
import edu.wpi.first.hal.JNIWrapper;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
@@ -24,4 +25,11 @@ public class CANJNI extends JNIWrapper {
|
||||
IntBuffer messageID, int messageIDMask, ByteBuffer timeStamp);
|
||||
|
||||
public static native void getCANStatus(CANStatus status);
|
||||
|
||||
public static native int openCANStreamSession(int messageID, int messageIDMask, int maxMessages);
|
||||
|
||||
public static native void closeCANStreamSession(int sessionHandle);
|
||||
|
||||
public static native int readCANStreamSession(
|
||||
int sessionHandle, CANStreamMessage[] messages, int messagesToRead);
|
||||
}
|
||||
|
||||
@@ -151,5 +151,9 @@ public class RoboRioDataJNI extends JNIWrapper {
|
||||
|
||||
public static native void setBrownoutVoltage(double brownoutVoltage);
|
||||
|
||||
public static native String getSerialNumber();
|
||||
|
||||
public static native void setSerialNumber(String serialNumber);
|
||||
|
||||
public static native void resetData();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ constexpr int32_t kExpectedLoopTiming = 40;
|
||||
* reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums
|
||||
* and get hot; by 5.0ms the hum is nearly continuous
|
||||
* - 10ms periods work well for Victor 884
|
||||
* - 5ms periods allows higher update rates for Luminary Micro Jaguar speed
|
||||
* - 5ms periods allows higher update rates for Luminary Micro Jaguar motor
|
||||
* controllers. Due to the shipping firmware on the Jaguar, we can't run the
|
||||
* update period less than 5.05 ms.
|
||||
*
|
||||
|
||||
@@ -179,6 +179,15 @@ void InitializeFRCDriverStation() {
|
||||
}
|
||||
} // namespace hal::init
|
||||
|
||||
namespace hal {
|
||||
static void DefaultPrintErrorImpl(const char* line, size_t size) {
|
||||
std::fwrite(line, size, 1, stderr);
|
||||
}
|
||||
} // namespace hal
|
||||
|
||||
static std::atomic<void (*)(const char* line, size_t size)> gPrintErrorImpl{
|
||||
hal::DefaultPrintErrorImpl};
|
||||
|
||||
extern "C" {
|
||||
|
||||
int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
@@ -256,7 +265,8 @@ int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
if (callStack && callStack[0] != '\0') {
|
||||
fmt::format_to(fmt::appender{buf}, "{}\n", callStack);
|
||||
}
|
||||
std::fwrite(buf.data(), buf.size(), 1, stderr);
|
||||
auto printError = gPrintErrorImpl.load();
|
||||
printError(buf.data(), buf.size());
|
||||
}
|
||||
if (i == KEEP_MSGS) {
|
||||
// replace the oldest one
|
||||
@@ -275,6 +285,10 @@ int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
return retval;
|
||||
}
|
||||
|
||||
void HAL_SetPrintErrorImpl(void (*func)(const char* line, size_t size)) {
|
||||
gPrintErrorImpl.store(func ? func : hal::DefaultPrintErrorImpl);
|
||||
}
|
||||
|
||||
int32_t HAL_SendConsoleLine(const char* line) {
|
||||
std::string_view lineRef{line};
|
||||
if (lineRef.size() <= 65535) {
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <FRC_NetworkCommunication/LoadOut.h>
|
||||
#include <FRC_NetworkCommunication/UsageReporting.h>
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/MemoryBuffer.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
@@ -270,6 +273,20 @@ int64_t HAL_GetFPGARevision(int32_t* status) {
|
||||
return global->readRevision(status);
|
||||
}
|
||||
|
||||
size_t HAL_GetSerialNumber(char* buffer, size_t size) {
|
||||
const char* serialNum = std::getenv("serialnum");
|
||||
if (serialNum) {
|
||||
std::strncpy(buffer, serialNum, size);
|
||||
buffer[size - 1] = '\0';
|
||||
return std::strlen(buffer);
|
||||
} else {
|
||||
if (size > 0) {
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t HAL_GetFPGATime(int32_t* status) {
|
||||
hal::init::CheckInit();
|
||||
if (!global) {
|
||||
|
||||
@@ -29,6 +29,19 @@ DEFINE_CAPI(int32_t, UserFaults5V, 0)
|
||||
DEFINE_CAPI(int32_t, UserFaults3V3, 0)
|
||||
DEFINE_CAPI(double, BrownoutVoltage, 6.75)
|
||||
|
||||
int32_t HALSIM_RegisterRoboRioSerialNumberCallback(
|
||||
HAL_RoboRioStringCallback callback, void* param, HAL_Bool initialNotify) {
|
||||
return 0;
|
||||
}
|
||||
void HALSIM_CancelRoboRioSerialNumberCallback(int32_t uid) {}
|
||||
size_t HALSIM_GetRoboRioSerialNumber(char* buffer, size_t size) {
|
||||
if (size > 0) {
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void HALSIM_SetRoboRioSerialNumber(const char* buffer, size_t size) {}
|
||||
|
||||
void HALSIM_RegisterRoboRioAllCallbacks(HAL_NotifyCallback callback,
|
||||
void* param, HAL_Bool initialNotify) {}
|
||||
} // extern "C"
|
||||
|
||||
@@ -91,4 +91,90 @@ Java_edu_wpi_first_hal_can_CANJNI_getCANStatus
|
||||
txFullCount, receiveErrorCount, transmitErrorCount);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_can_CANJNI
|
||||
* Method: openCANStreamSession
|
||||
* Signature: (III)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_can_CANJNI_openCANStreamSession
|
||||
(JNIEnv* env, jclass, jint messageID, jint messageIDMask, jint maxMessages)
|
||||
{
|
||||
uint32_t handle = 0;
|
||||
int32_t status = 0;
|
||||
HAL_CAN_OpenStreamSession(&handle, static_cast<uint32_t>(messageID),
|
||||
static_cast<uint32_t>(messageIDMask),
|
||||
static_cast<uint32_t>(maxMessages), &status);
|
||||
|
||||
if (!CheckStatus(env, status)) {
|
||||
return static_cast<jint>(0);
|
||||
}
|
||||
|
||||
return static_cast<jint>(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_can_CANJNI
|
||||
* Method: closeCANStreamSession
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_can_CANJNI_closeCANStreamSession
|
||||
(JNIEnv* env, jclass, jint sessionHandle)
|
||||
{
|
||||
HAL_CAN_CloseStreamSession(static_cast<uint32_t>(sessionHandle));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_can_CANJNI
|
||||
* Method: readCANStreamSession
|
||||
* Signature: (I[Ljava/lang/Object;I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_can_CANJNI_readCANStreamSession
|
||||
(JNIEnv* env, jclass, jint sessionHandle, jobjectArray messages,
|
||||
jint messagesToRead)
|
||||
{
|
||||
uint32_t handle = static_cast<uint32_t>(sessionHandle);
|
||||
uint32_t messagesRead = 0;
|
||||
|
||||
wpi::SmallVector<HAL_CANStreamMessage, 16> messageBuffer;
|
||||
messageBuffer.resize_for_overwrite(messagesToRead);
|
||||
|
||||
int32_t status = 0;
|
||||
|
||||
HAL_CAN_ReadStreamSession(handle, messageBuffer.begin(),
|
||||
static_cast<uint32_t>(messagesToRead),
|
||||
&messagesRead, &status);
|
||||
|
||||
if (status == HAL_ERR_CANSessionMux_MessageNotFound || messagesRead == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!CheckStatus(env, status)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < static_cast<int>(messagesRead); i++) {
|
||||
struct HAL_CANStreamMessage* msg = &messageBuffer[i];
|
||||
JLocal<jobject> elem{
|
||||
env, static_cast<jstring>(env->GetObjectArrayElement(messages, i))};
|
||||
if (!elem) {
|
||||
// TODO decide if should throw
|
||||
continue;
|
||||
}
|
||||
JLocal<jbyteArray> toSetArray{
|
||||
env, SetCANStreamObject(env, elem, msg->dataSize, msg->messageID,
|
||||
msg->timeStamp)};
|
||||
auto javaLen = env->GetArrayLength(toSetArray);
|
||||
if (javaLen < msg->dataSize) {
|
||||
msg->dataSize = javaLen;
|
||||
}
|
||||
env->SetByteArrayRegion(toSetArray, 0, msg->dataSize,
|
||||
reinterpret_cast<jbyte*>(msg->data));
|
||||
}
|
||||
|
||||
return static_cast<jint>(messagesRead);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -52,6 +52,7 @@ static JClass canStatusCls;
|
||||
static JClass matchInfoDataCls;
|
||||
static JClass accumulatorResultCls;
|
||||
static JClass canDataCls;
|
||||
static JClass canStreamMessageCls;
|
||||
static JClass halValueCls;
|
||||
static JClass baseStoreCls;
|
||||
static JClass revPHVersionCls;
|
||||
@@ -64,6 +65,7 @@ static const JClassInit classes[] = {
|
||||
{"edu/wpi/first/hal/MatchInfoData", &matchInfoDataCls},
|
||||
{"edu/wpi/first/hal/AccumulatorResult", &accumulatorResultCls},
|
||||
{"edu/wpi/first/hal/CANData", &canDataCls},
|
||||
{"edu/wpi/first/hal/CANStreamMessage", &canStreamMessageCls},
|
||||
{"edu/wpi/first/hal/HALValue", &halValueCls},
|
||||
{"edu/wpi/first/hal/DMAJNISample$BaseStore", &baseStoreCls},
|
||||
{"edu/wpi/first/hal/REVPHVersion", &revPHVersionCls}};
|
||||
@@ -303,6 +305,18 @@ jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length,
|
||||
return retVal;
|
||||
}
|
||||
|
||||
jbyteArray SetCANStreamObject(JNIEnv* env, jobject canStreamData,
|
||||
int32_t length, uint32_t messageID,
|
||||
uint64_t timestamp) {
|
||||
static jmethodID func =
|
||||
env->GetMethodID(canStreamMessageCls, "setStreamData", "(IIJ)[B");
|
||||
|
||||
jbyteArray retVal = static_cast<jbyteArray>(env->CallObjectMethod(
|
||||
canStreamData, func, static_cast<jint>(length),
|
||||
static_cast<jint>(messageID), static_cast<jlong>(timestamp)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
jobject CreateHALValue(JNIEnv* env, const HAL_Value& value) {
|
||||
static jmethodID fromNative = env->GetStaticMethodID(
|
||||
halValueCls, "fromNative", "(IJD)Ledu/wpi/first/hal/HALValue;");
|
||||
@@ -442,6 +456,20 @@ Java_edu_wpi_first_hal_HALUtil_getFPGARevision
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HALUtil
|
||||
* Method: getSerialNumber
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_HALUtil_getSerialNumber
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
char serialNum[9];
|
||||
size_t len = HAL_GetSerialNumber(serialNum, sizeof(serialNum));
|
||||
return MakeJString(env, std::string_view(serialNum, len));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_HALUtil
|
||||
* Method: getFPGATime
|
||||
|
||||
@@ -78,6 +78,10 @@ void SetAccumulatorResultObject(JNIEnv* env, jobject accumulatorResult,
|
||||
jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length,
|
||||
uint64_t timestamp);
|
||||
|
||||
jbyteArray SetCANStreamObject(JNIEnv* env, jobject canStreamData,
|
||||
int32_t length, uint32_t messageID,
|
||||
uint64_t timestamp);
|
||||
|
||||
jobject CreateHALValue(JNIEnv* env, const HAL_Value& value);
|
||||
|
||||
jobject CreateDMABaseStore(JNIEnv* env, jint valueType, jint index);
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <wpi/jni_util.h>
|
||||
|
||||
#include "CallbackStore.h"
|
||||
#include "edu_wpi_first_hal_simulation_RoboRioDataJNI.h"
|
||||
#include "hal/simulation/RoboRioData.h"
|
||||
|
||||
using namespace hal;
|
||||
using namespace wpi::java;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -825,6 +828,34 @@ Java_edu_wpi_first_hal_simulation_RoboRioDataJNI_setBrownoutVoltage
|
||||
HALSIM_SetRoboRioBrownoutVoltage(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_RoboRioDataJNI
|
||||
* Method: getSerialNumber
|
||||
* Signature: ()Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_RoboRioDataJNI_getSerialNumber
|
||||
(JNIEnv* env, jclass)
|
||||
{
|
||||
char serialNum[9];
|
||||
size_t len = HALSIM_GetRoboRioSerialNumber(serialNum, sizeof(serialNum));
|
||||
return MakeJString(env, std::string_view(serialNum, len));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_RoboRioDataJNI
|
||||
* Method: setSerialNumber
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_RoboRioDataJNI_setSerialNumber
|
||||
(JNIEnv* env, jclass, jstring serialNumber)
|
||||
{
|
||||
JStringRef serialNumberJString{env, serialNumber};
|
||||
HALSIM_SetRoboRioSerialNumber(serialNumberJString.c_str(),
|
||||
serialNumberJString.size());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_RoboRioDataJNI
|
||||
* Method: resetData
|
||||
|
||||
@@ -54,7 +54,8 @@ HAL_ENUM(HAL_CANManufacturer) {
|
||||
HAL_CAN_Man_kKauaiLabs = 9,
|
||||
HAL_CAN_Man_kCopperforge = 10,
|
||||
HAL_CAN_Man_kPWF = 11,
|
||||
HAL_CAN_Man_kStudica = 12
|
||||
HAL_CAN_Man_kStudica = 12,
|
||||
HAL_CAN_Man_kTheThriftyBot = 13
|
||||
};
|
||||
// clang-format on
|
||||
/** @} */
|
||||
|
||||
@@ -36,6 +36,14 @@ extern "C" {
|
||||
int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
const char* details, const char* location,
|
||||
const char* callStack, HAL_Bool printMsg);
|
||||
|
||||
/**
|
||||
* Set the print function used by HAL_SendError
|
||||
*
|
||||
* @param func Function called by HAL_SendError when stderr is printed
|
||||
*/
|
||||
void HAL_SetPrintErrorImpl(void (*func)(const char* line, size_t size));
|
||||
|
||||
/**
|
||||
* Sends a line to the driver station console.
|
||||
*
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
* @defgroup hal_extensions Simulator Extensions
|
||||
* @ingroup hal_capi
|
||||
* HAL Simulator Extensions. These are libraries that provide additional
|
||||
* simulator functionality, such as a Gazebo interface, or a more light weight
|
||||
* simulation.
|
||||
* simulator functionality.
|
||||
*
|
||||
* An extension must expose the HALSIM_InitExtension entry point which is
|
||||
* invoked after the library is loaded.
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstddef>
|
||||
#else
|
||||
|
||||
#include <stddef.h> // NOLINT(build/include_order)
|
||||
|
||||
#endif
|
||||
|
||||
#include "hal/Types.h"
|
||||
|
||||
/**
|
||||
@@ -66,6 +74,13 @@ int32_t HAL_GetFPGAVersion(int32_t* status);
|
||||
*/
|
||||
int64_t HAL_GetFPGARevision(int32_t* status);
|
||||
|
||||
/**
|
||||
* Returns the serial number.
|
||||
*
|
||||
* @return Serial number.
|
||||
*/
|
||||
size_t HAL_GetSerialNumber(char* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* Returns the runtime type of the HAL.
|
||||
*
|
||||
|
||||
@@ -4,9 +4,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "hal/Types.h"
|
||||
#include "hal/simulation/NotifyListener.h"
|
||||
|
||||
typedef void (*HAL_RoboRioStringCallback)(const char* name, void* param,
|
||||
const char* str, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -121,6 +126,12 @@ void HALSIM_CancelRoboRioBrownoutVoltageCallback(int32_t uid);
|
||||
double HALSIM_GetRoboRioBrownoutVoltage(void);
|
||||
void HALSIM_SetRoboRioBrownoutVoltage(double brownoutVoltage);
|
||||
|
||||
int32_t HALSIM_RegisterRoboRioSerialNumberCallback(
|
||||
HAL_RoboRioStringCallback callback, void* param, HAL_Bool initialNotify);
|
||||
void HALSIM_CancelRoboRioSerialNumberCallback(int32_t uid);
|
||||
size_t HALSIM_GetRoboRioSerialNumber(char* buffer, size_t size);
|
||||
void HALSIM_SetRoboRioSerialNumber(const char* serialNumber, size_t size);
|
||||
|
||||
void HALSIM_RegisterRoboRioAllCallbacks(HAL_NotifyCallback callback,
|
||||
void* param, HAL_Bool initialNotify);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ constexpr int32_t kExpectedLoopTiming = 40;
|
||||
* reliably down to 10.0 ms; starting at about 8.5ms, the servo sometimes hums
|
||||
* and get hot; by 5.0ms the hum is nearly continuous
|
||||
* - 10ms periods work well for Victor 884
|
||||
* - 5ms periods allows higher update rates for Luminary Micro Jaguar speed
|
||||
* - 5ms periods allows higher update rates for Luminary Micro Jaguar motor
|
||||
* controllers. Due to the shipping firmware on the Jaguar, we can't run the
|
||||
* update period less than 5.05 ms.
|
||||
*
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -86,6 +87,15 @@ void InitializeDriverStation() {
|
||||
}
|
||||
} // namespace hal::init
|
||||
|
||||
namespace hal {
|
||||
static void DefaultPrintErrorImpl(const char* line, size_t size) {
|
||||
std::fwrite(line, size, 1, stderr);
|
||||
}
|
||||
} // namespace hal
|
||||
|
||||
static std::atomic<void (*)(const char* line, size_t size)> gPrintErrorImpl{
|
||||
hal::DefaultPrintErrorImpl};
|
||||
|
||||
extern "C" {
|
||||
|
||||
void HALSIM_SetSendError(HALSIM_SendErrorHandler handler) {
|
||||
@@ -138,7 +148,8 @@ int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
if (callStack && callStack[0] != '\0') {
|
||||
fmt::format_to(fmt::appender{buf}, "{}\n", callStack);
|
||||
}
|
||||
std::fwrite(buf.data(), buf.size(), 1, stderr);
|
||||
auto printError = gPrintErrorImpl.load();
|
||||
printError(buf.data(), buf.size());
|
||||
}
|
||||
if (i == KEEP_MSGS) {
|
||||
// replace the oldest one
|
||||
@@ -157,6 +168,10 @@ int32_t HAL_SendError(HAL_Bool isError, int32_t errorCode, HAL_Bool isLVCode,
|
||||
return retval;
|
||||
}
|
||||
|
||||
void HAL_SetPrintErrorImpl(void (*func)(const char* line, size_t size)) {
|
||||
gPrintErrorImpl.store(func ? func : hal::DefaultPrintErrorImpl);
|
||||
}
|
||||
|
||||
int32_t HAL_SendConsoleLine(const char* line) {
|
||||
auto handler = sendConsoleLineHandler.load();
|
||||
if (handler) {
|
||||
|
||||
@@ -280,6 +280,10 @@ int64_t HAL_GetFPGARevision(int32_t* status) {
|
||||
return 0; // TODO: Find a better number to return;
|
||||
}
|
||||
|
||||
size_t HAL_GetSerialNumber(char* buffer, size_t size) {
|
||||
return HALSIM_GetRoboRioSerialNumber(buffer, size);
|
||||
}
|
||||
|
||||
uint64_t HAL_GetFPGATime(int32_t* status) {
|
||||
return hal::GetFPGATime();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,44 @@ void RoboRioData::ResetData() {
|
||||
userFaults5V.Reset(0);
|
||||
userFaults3V3.Reset(0);
|
||||
brownoutVoltage.Reset(6.75);
|
||||
m_serialNumber = "";
|
||||
}
|
||||
|
||||
int32_t RoboRioData::RegisterSerialNumberCallback(
|
||||
HAL_RoboRioStringCallback callback, void* param, HAL_Bool initialNotify) {
|
||||
std::scoped_lock lock(m_serialNumberMutex);
|
||||
int32_t uid = m_serialNumberCallbacks.Register(callback, param);
|
||||
if (initialNotify) {
|
||||
callback(GetSerialNumberName(), param, m_serialNumber.c_str(),
|
||||
m_serialNumber.size());
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
void RoboRioData::CancelSerialNumberCallback(int32_t uid) {
|
||||
m_serialNumberCallbacks.Cancel(uid);
|
||||
}
|
||||
|
||||
size_t RoboRioData::GetSerialNumber(char* buffer, size_t size) {
|
||||
std::scoped_lock lock(m_serialNumberMutex);
|
||||
size_t copied = m_serialNumber.copy(buffer, size);
|
||||
// Null terminate
|
||||
if (copied == size) {
|
||||
copied -= 1;
|
||||
}
|
||||
buffer[copied] = '\0';
|
||||
return copied;
|
||||
}
|
||||
|
||||
void RoboRioData::SetSerialNumber(const char* serialNumber, size_t size) {
|
||||
// Limit serial number to 8 characters internally- serialnum environment
|
||||
// variable is always 8 characters
|
||||
if (size > 8) {
|
||||
size = 8;
|
||||
}
|
||||
std::scoped_lock lock(m_serialNumberMutex);
|
||||
m_serialNumber = std::string(serialNumber, size);
|
||||
m_serialNumberCallbacks(m_serialNumber.c_str(), m_serialNumber.size());
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -60,6 +98,24 @@ DEFINE_CAPI(int32_t, UserFaults5V, userFaults5V)
|
||||
DEFINE_CAPI(int32_t, UserFaults3V3, userFaults3V3)
|
||||
DEFINE_CAPI(double, BrownoutVoltage, brownoutVoltage)
|
||||
|
||||
int32_t HALSIM_RegisterRoboRioSerialNumberCallback(
|
||||
HAL_RoboRioStringCallback callback, void* param, HAL_Bool initialNotify) {
|
||||
return SimRoboRioData->RegisterSerialNumberCallback(callback, param,
|
||||
initialNotify);
|
||||
}
|
||||
void HALSIM_CancelRoboRioSerialNumberCallback(int32_t uid) {
|
||||
return SimRoboRioData->CancelSerialNumberCallback(uid);
|
||||
}
|
||||
size_t HALSIM_GetRoboRioSerialNumber(char* buffer, size_t size) {
|
||||
return SimRoboRioData->GetSerialNumber(buffer, size);
|
||||
}
|
||||
void HALSIM_SetRoboRioSerialNumber(const char* serialNumber, size_t size) {
|
||||
SimRoboRioData->SetSerialNumber(serialNumber, size);
|
||||
}
|
||||
|
||||
void HALSIM_RegisterRoboRioAllCallbacks(HAL_NotifyCallback callback,
|
||||
void* param, HAL_Bool initialNotify);
|
||||
|
||||
#define REGISTER(NAME) \
|
||||
SimRoboRioData->NAME.RegisterCallback(callback, param, initialNotify)
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <wpi/spinlock.h>
|
||||
|
||||
#include "hal/simulation/RoboRioData.h"
|
||||
#include "hal/simulation/SimDataValue.h"
|
||||
|
||||
@@ -26,6 +31,8 @@ class RoboRioData {
|
||||
HAL_SIMDATAVALUE_DEFINE_NAME(UserFaults3V3)
|
||||
HAL_SIMDATAVALUE_DEFINE_NAME(BrownoutVoltage)
|
||||
|
||||
HAL_SIMCALLBACKREGISTRY_DEFINE_NAME(SerialNumber)
|
||||
|
||||
public:
|
||||
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetFPGAButtonName> fpgaButton{false};
|
||||
SimDataValue<double, HAL_MakeDouble, GetVInVoltageName> vInVoltage{12.0};
|
||||
@@ -50,7 +57,20 @@ class RoboRioData {
|
||||
SimDataValue<double, HAL_MakeDouble, GetBrownoutVoltageName> brownoutVoltage{
|
||||
6.75};
|
||||
|
||||
int32_t RegisterSerialNumberCallback(HAL_RoboRioStringCallback callback,
|
||||
void* param, HAL_Bool initialNotify);
|
||||
void CancelSerialNumberCallback(int32_t uid);
|
||||
size_t GetSerialNumber(char* buffer, size_t size);
|
||||
void SetSerialNumber(const char* serialNumber, size_t size);
|
||||
|
||||
virtual void ResetData();
|
||||
|
||||
private:
|
||||
wpi::spinlock m_serialNumberMutex;
|
||||
std::string m_serialNumber;
|
||||
|
||||
SimCallbackRegistry<HAL_RoboRioStringCallback, GetSerialNumberName>
|
||||
m_serialNumberCallbacks;
|
||||
};
|
||||
extern RoboRioData* SimRoboRioData;
|
||||
} // namespace hal
|
||||
|
||||
20
imgui/.styleguide
Normal file
20
imgui/.styleguide
Normal file
@@ -0,0 +1,20 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
\.inc$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
}
|
||||
|
||||
generatedFileExclude {
|
||||
}
|
||||
|
||||
repoRootNameOverride {
|
||||
}
|
||||
|
||||
includeOtherLibs {
|
||||
}
|
||||
@@ -1,74 +1,106 @@
|
||||
# Download and unpack imgui at configure time
|
||||
configure_file(CMakeLists.txt.in imgui-download/CMakeLists.txt)
|
||||
INCLUDE(FetchContent)
|
||||
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/imgui-download )
|
||||
if(result)
|
||||
message(FATAL_ERROR "CMake step for imgui failed: ${result}")
|
||||
endif()
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build .
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/imgui-download )
|
||||
if(result)
|
||||
message(FATAL_ERROR "Build step for imgui failed: ${result}")
|
||||
FetchContent_Declare(
|
||||
glfw3
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 6b57e08bb0078c9834889eab871bac2368198c15
|
||||
)
|
||||
FetchContent_Declare(
|
||||
gl3w
|
||||
GIT_REPOSITORY https://github.com/skaslev/gl3w
|
||||
GIT_TAG 5f8d7fd191ba22ff2b60c1106d7135bb9a335533
|
||||
)
|
||||
FetchContent_Declare(
|
||||
imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG 3ea0fad204e994d669f79ed29dcaf61cd5cb571d
|
||||
)
|
||||
FetchContent_Declare(
|
||||
implot
|
||||
GIT_REPOSITORY https://github.com/epezent/implot.git
|
||||
GIT_TAG e80e42e8b4136ddb84ccfe04fa28d0c745828952
|
||||
)
|
||||
FetchContent_Declare(
|
||||
fonts
|
||||
URL https://github.com/wpilibsuite/thirdparty-fonts/releases/download/v0.2/fonts.zip
|
||||
URL_HASH SHA256=cedf365657fab0770e11f72d49e4f0f889f564d2e635a4d214029d0ab6bcd324
|
||||
)
|
||||
FetchContent_Declare(
|
||||
stb
|
||||
GIT_REPOSITORY https://github.com/nothings/stb.git
|
||||
GIT_TAG c9064e317699d2e495f36ba4f9ac037e88ee371a
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(
|
||||
imgui
|
||||
implot
|
||||
fonts
|
||||
stb
|
||||
)
|
||||
|
||||
# Add glfw directly to our build.
|
||||
FetchContent_GetProperties(glfw3)
|
||||
if(NOT glfw3_POPULATED)
|
||||
FetchContent_Populate(glfw3)
|
||||
set(SAVE_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(GLFW_INSTALL OFF)
|
||||
add_subdirectory(${glfw3_SOURCE_DIR} ${glfw3_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
set_property(TARGET glfw PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
set(BUILD_SHARED_LIBS ${SAVE_BUILD_SHARED_LIBS})
|
||||
endif()
|
||||
|
||||
# Build font
|
||||
add_executable(imgui_font_bin2c ${CMAKE_CURRENT_BINARY_DIR}/imgui-src/misc/fonts/binary_to_compressed_c.cpp)
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ProggyDotted.inc
|
||||
COMMAND imgui_font_bin2c
|
||||
ARGS "${CMAKE_CURRENT_BINARY_DIR}/proggyfonts-src/ProggyDotted/ProggyDotted Regular.ttf" ProggyDotted > ${CMAKE_CURRENT_BINARY_DIR}/ProggyDotted.inc
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
MAIN_DEPENDENCY "${CMAKE_CURRENT_BINARY_DIR}/proggyfonts-src/ProggyDotted/ProggyDotted Regular.ttf"
|
||||
VERBATIM
|
||||
)
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/imgui_ProggyDotted.cpp
|
||||
CONTENT "#include \"imgui_ProggyDotted.h\"\n#include \"ProggyDotted.inc\"\nImFont* ImGui::AddFontProggyDotted(ImGuiIO& io, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) {\n return io.Fonts->AddFontFromMemoryCompressedTTF(ProggyDotted_compressed_data, ProggyDotted_compressed_size, size_pixels, font_cfg, glyph_ranges);\n}\n"
|
||||
)
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/imgui_ProggyDotted.h
|
||||
CONTENT "#pragma once\n#include \"imgui.h\"\nnamespace ImGui {\nImFont* AddFontProggyDotted(ImGuiIO& io, float size_pixels, const ImFontConfig* font_cfg = nullptr, const ImWchar* glyph_ranges = nullptr);\n}\n"
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/imgui_ProggyDotted.cpp
|
||||
PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ProggyDotted.inc)
|
||||
|
||||
# stb_image
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/stb_image.cpp
|
||||
CONTENT "#define STBI_WINDOWS_UTF8\n#define STB_IMAGE_IMPLEMENTATION\n#include \"stb_image.h\"\n"
|
||||
)
|
||||
# Don't use gl3w CMakeLists.txt due to https://github.com/skaslev/gl3w/issues/66
|
||||
FetchContent_GetProperties(gl3w)
|
||||
if(NOT gl3w_POPULATED)
|
||||
FetchContent_Populate(gl3w)
|
||||
endif()
|
||||
if(NOT EXISTS "${gl3w_BINARY_DIR}/src/gl3w.c")
|
||||
find_package(Python COMPONENTS Interpreter Development REQUIRED)
|
||||
execute_process(
|
||||
COMMAND "${Python_EXECUTABLE}" ${gl3w_SOURCE_DIR}/gl3w_gen.py "--root=${gl3w_BINARY_DIR}"
|
||||
WORKING_DIRECTORY ${gl3w_BINARY_DIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add imgui directly to our build.
|
||||
set(SAVE_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(GLFW_INSTALL OFF)
|
||||
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/glfw-src
|
||||
${CMAKE_CURRENT_BINARY_DIR}/glfw-build
|
||||
EXCLUDE_FROM_ALL)
|
||||
set_property(TARGET glfw PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
set(BUILD_SHARED_LIBS ${SAVE_BUILD_SHARED_LIBS})
|
||||
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/gl3w-src
|
||||
${CMAKE_CURRENT_BINARY_DIR}/gl3w-build
|
||||
EXCLUDE_FROM_ALL)
|
||||
|
||||
set(imgui_srcdir ${CMAKE_CURRENT_BINARY_DIR}/imgui-src)
|
||||
file(GLOB imgui_sources ${imgui_srcdir}/*.cpp ${imgui_srcdir}/misc/cpp/*.cpp)
|
||||
set(implot_srcdir ${CMAKE_CURRENT_BINARY_DIR}/implot-src)
|
||||
file(GLOB implot_sources ${implot_srcdir}/*.cpp)
|
||||
add_library(imgui STATIC ${imgui_sources} ${implot_sources} ${imgui_srcdir}/backends/imgui_impl_glfw.cpp ${imgui_srcdir}/backends/imgui_impl_opengl3.cpp ${CMAKE_CURRENT_BINARY_DIR}/imgui_ProggyDotted.cpp ${CMAKE_CURRENT_BINARY_DIR}/stb_image.cpp)
|
||||
file(GLOB imgui_sources ${imgui_SOURCE_DIR}/*.cpp ${imgui_SOURCE_DIR}/misc/cpp/*.cpp)
|
||||
file(GLOB implot_sources ${implot_SOURCE_DIR}/*.cpp)
|
||||
file(GLOB fonts_sources ${fonts_SOURCE_DIR}/src/*.cpp)
|
||||
add_library(imgui STATIC
|
||||
${imgui_sources}
|
||||
${implot_sources}
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
${gl3w_BINARY_DIR}/src/gl3w.c
|
||||
${fonts_sources}
|
||||
src/stb_image.cpp
|
||||
)
|
||||
target_compile_definitions(imgui PUBLIC IMGUI_IMPL_OPENGL_LOADER_GL3W)
|
||||
if (MSVC)
|
||||
target_sources(imgui PRIVATE ${imgui_srcdir}/backends/imgui_impl_dx11.cpp)
|
||||
target_sources(imgui PRIVATE ${imgui_SOURCE_DIR}/backends/imgui_impl_dx11.cpp)
|
||||
else()
|
||||
if (APPLE)
|
||||
target_compile_options(imgui PRIVATE -fobjc-arc)
|
||||
set_target_properties(imgui PROPERTIES LINK_FLAGS "-framework Metal -framework QuartzCore")
|
||||
target_sources(imgui PRIVATE ${imgui_srcdir}/backends/imgui_impl_metal.mm)
|
||||
target_sources(imgui PRIVATE ${imgui_SOURCE_DIR}/backends/imgui_impl_metal.mm)
|
||||
else()
|
||||
#target_sources(imgui PRIVATE ${imgui_srcdir}/backends/imgui_impl_opengl3.cpp)
|
||||
#target_sources(imgui PRIVATE ${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp)
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(imgui PUBLIC gl3w glfw)
|
||||
target_include_directories(imgui PUBLIC "$<BUILD_INTERFACE:${imgui_srcdir}>" "$<BUILD_INTERFACE:${imgui_srcdir}/misc/cpp>" "$<BUILD_INTERFACE:${implot_srcdir}>" "$<BUILD_INTERFACE:${imgui_srcdir}/backends>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/stb-src>")
|
||||
target_link_libraries(imgui PUBLIC glfw)
|
||||
target_include_directories(imgui
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${imgui_SOURCE_DIR}>"
|
||||
"$<BUILD_INTERFACE:${imgui_SOURCE_DIR}/misc/cpp>"
|
||||
"$<BUILD_INTERFACE:${implot_SOURCE_DIR}>"
|
||||
"$<BUILD_INTERFACE:${imgui_SOURCE_DIR}/backends>"
|
||||
"$<BUILD_INTERFACE:${gl3w_BINARY_DIR}/include>"
|
||||
"$<BUILD_INTERFACE:${fonts_SOURCE_DIR}/include>"
|
||||
"$<BUILD_INTERFACE:${stb_SOURCE_DIR}>"
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
|
||||
)
|
||||
|
||||
set_property(TARGET imgui PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_compile_features(imgui PUBLIC cxx_std_20)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.3.0)
|
||||
|
||||
project(imgui-download NONE)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(glfw3
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 6b57e08bb0078c9834889eab871bac2368198c15
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/glfw-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/glfw-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Add(gl3w
|
||||
GIT_REPOSITORY https://github.com/skaslev/gl3w
|
||||
GIT_TAG 5f8d7fd191ba22ff2b60c1106d7135bb9a335533
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/gl3w-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/gl3w-build"
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Add(imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG aceab9a877de0258d19d29a5d87a51b63a8999bf
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/imgui-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/imgui-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Add(implot
|
||||
GIT_REPOSITORY https://github.com/epezent/implot.git
|
||||
GIT_TAG e80e42e8b4136ddb84ccfe04fa28d0c745828952
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Add(proggyfonts
|
||||
GIT_REPOSITORY https://github.com/bluescan/proggyfonts.git
|
||||
GIT_TAG v1.1.5
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/proggyfonts-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/proggyfonts-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
ExternalProject_Add(stb
|
||||
GIT_REPOSITORY https://github.com/nothings/stb.git
|
||||
GIT_TAG c9064e317699d2e495f36ba4f9ac037e88ee371a
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/stb-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/stb-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
@@ -2,12 +2,6 @@
|
||||
// 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
|
||||
|
||||
class Switch {
|
||||
public:
|
||||
virtual ~Switch() = default;
|
||||
|
||||
/// \brief Returns true when the switch is triggered.
|
||||
virtual bool Get() = 0;
|
||||
};
|
||||
#define STBI_WINDOWS_UTF8
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
@@ -297,8 +297,13 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
if (it.component.name == "${nativeName}JNI") {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
} else {
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ The MessagePack contents shall be an array of maps. Each map in the array shall
|
||||
|
||||
Both clients and servers shall support unsecure connections (`ws:`) and may support secure connections (`wss:`). In a trusted network environment (e.g. a robot network), clients that support secure connections should fall back to an unsecure connection if a secure connection is not available.
|
||||
|
||||
Servers shall support a resource name of `/nt/<name>`, where `<name>` is an arbitrary string representing the client name. The client name must be unique (for a particular server). Servers shall reject duplicate connections to the same resource name by responding with HTTP Error Code 409 (Conflict). Clients should provide a way to specify the resource name (in particular, the client name portion) and should provide a mechanism to make the name unique (e.g. by suffixing the name with a unique identifier).
|
||||
Servers shall support a resource name of `/nt/<name>`, where `<name>` is an arbitrary string representing the client name. The client name does not need to be unique; multiple connections to the same name are allowed; the server shall ensure the name is unique (for the purposes of meta-topics) by appending a '@' and a unique number (if necessary). To support this, the name provided by the client should not contain an embedded '@'. Clients should provide a way to specify the resource name (in particular, the client name portion).
|
||||
|
||||
Both clients and servers shall support/use subprotocol `networktables.first.wpi.edu` for this protocol. Clients and servers shall terminate the connection in accordance with the WebSocket protocol unless both sides support this subprotocol.
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ public final class NetworkTablesJNI {
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static double[] pubSubOptionValues(PubSubOption... options) {
|
||||
double[] rv = new double[options.length];
|
||||
private static int[] pubSubOptionValues(PubSubOption... options) {
|
||||
int[] rv = new int[options.length];
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
rv[i] = options[i].m_value;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public final class NetworkTablesJNI {
|
||||
public static native int getEntry(int inst, String key);
|
||||
|
||||
private static native int getEntry(
|
||||
int topic, int type, String typeStr, int[] optionTypes, double[] optionValues);
|
||||
int topic, int type, String typeStr, int[] optionTypes, int[] optionValues);
|
||||
|
||||
public static int getEntry(int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return getEntry(topic, type, typeStr, pubSubOptionTypes(options), pubSubOptionValues(options));
|
||||
@@ -137,7 +137,7 @@ public final class NetworkTablesJNI {
|
||||
public static native void setTopicProperties(int topic, String properties);
|
||||
|
||||
private static native int subscribe(
|
||||
int topic, int type, String typeStr, int[] optionTypes, double[] optionValues);
|
||||
int topic, int type, String typeStr, int[] optionTypes, int[] optionValues);
|
||||
|
||||
public static int subscribe(int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return subscribe(topic, type, typeStr, pubSubOptionTypes(options), pubSubOptionValues(options));
|
||||
@@ -146,14 +146,14 @@ public final class NetworkTablesJNI {
|
||||
public static native void unsubscribe(int sub);
|
||||
|
||||
private static native int publish(
|
||||
int topic, int type, String typeStr, int[] optionTypes, double[] optionValues);
|
||||
int topic, int type, String typeStr, int[] optionTypes, int[] optionValues);
|
||||
|
||||
public static int publish(int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return publish(topic, type, typeStr, pubSubOptionTypes(options), pubSubOptionValues(options));
|
||||
}
|
||||
|
||||
private static native int publishEx(
|
||||
int topic, int type, String typeStr, String properties, int[] optionTypes, double[] optionValues);
|
||||
int topic, int type, String typeStr, String properties, int[] optionTypes, int[] optionValues);
|
||||
|
||||
public static int publishEx(int topic, int type, String typeStr, String properties, PubSubOption... options) {
|
||||
return publishEx(topic, type, typeStr, properties, pubSubOptionTypes(options), pubSubOptionValues(options));
|
||||
@@ -168,7 +168,7 @@ public final class NetworkTablesJNI {
|
||||
public static native int getTopicFromHandle(int pubsubentry);
|
||||
|
||||
private static native int subscribeMultiple(
|
||||
int inst, String[] prefixes, int[] optionTypes, double[] optionValues);
|
||||
int inst, String[] prefixes, int[] optionTypes, int[] optionValues);
|
||||
|
||||
public static int subscribeMultiple(int inst, String[] prefixes, PubSubOption... options) {
|
||||
return subscribeMultiple(
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
package edu.wpi.first.networktables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** A network table that knows its subtable path. */
|
||||
public final class NetworkTable {
|
||||
@@ -20,6 +22,7 @@ public final class NetworkTable {
|
||||
private final String m_path;
|
||||
private final String m_pathWithSep;
|
||||
private final NetworkTableInstance m_inst;
|
||||
private final MultiSubscriber m_topicSub;
|
||||
|
||||
/**
|
||||
* Gets the "base name" of a key. For example, "/foo/bar" becomes "bar". If the key has a trailing
|
||||
@@ -112,6 +115,8 @@ public final class NetworkTable {
|
||||
m_path = path;
|
||||
m_pathWithSep = path + PATH_SEPARATOR;
|
||||
m_inst = inst;
|
||||
m_topicSub =
|
||||
new MultiSubscriber(inst, new String[] {m_pathWithSep}, PubSubOption.topicsOnly(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -445,6 +450,123 @@ public final class NetworkTable {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
/** A listener that listens to events on topics in a {@link NetworkTable}. */
|
||||
@FunctionalInterface
|
||||
public interface TableEventListener {
|
||||
/**
|
||||
* Called when an event occurs on a topic in a {@link NetworkTable}.
|
||||
*
|
||||
* @param table the table the topic exists in
|
||||
* @param key the key associated with the topic that changed
|
||||
* @param event the event
|
||||
*/
|
||||
void accept(NetworkTable table, String key, NetworkTableEvent event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to topics only within this table.
|
||||
*
|
||||
* @param eventKinds set of event kinds to listen to
|
||||
* @param listener listener to add
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(EnumSet<NetworkTableEvent.Kind> eventKinds, TableEventListener listener) {
|
||||
final int prefixLen = m_path.length() + 1;
|
||||
return m_inst.addListener(
|
||||
new String[] {m_pathWithSep},
|
||||
eventKinds,
|
||||
event -> {
|
||||
String topicName = null;
|
||||
if (event.topicInfo != null) {
|
||||
topicName = event.topicInfo.name;
|
||||
} else if (event.valueData != null) {
|
||||
topicName = event.valueData.getTopic().getName();
|
||||
}
|
||||
if (topicName == null) {
|
||||
return;
|
||||
}
|
||||
String relativeKey = topicName.substring(prefixLen);
|
||||
if (relativeKey.indexOf(PATH_SEPARATOR) != -1) {
|
||||
// part of a sub table
|
||||
return;
|
||||
}
|
||||
listener.accept(this, relativeKey, event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to a single key.
|
||||
*
|
||||
* @param key the key name
|
||||
* @param eventKinds set of event kinds to listen to
|
||||
* @param listener listener to add
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addListener(
|
||||
String key, EnumSet<NetworkTableEvent.Kind> eventKinds, TableEventListener listener) {
|
||||
NetworkTableEntry entry = getEntry(key);
|
||||
return m_inst.addListener(entry, eventKinds, event -> listener.accept(this, key, event));
|
||||
}
|
||||
|
||||
/** A listener that listens to new tables in a {@link NetworkTable}. */
|
||||
@FunctionalInterface
|
||||
public interface SubTableListener {
|
||||
/**
|
||||
* Called when a new table is created within a {@link NetworkTable}.
|
||||
*
|
||||
* @param parent the parent of the table
|
||||
* @param name the name of the new table
|
||||
* @param table the new table
|
||||
*/
|
||||
void tableCreated(NetworkTable parent, String name, NetworkTable table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for sub-table creation. This calls the listener once for each newly created sub-table.
|
||||
* It immediately calls the listener for any existing sub-tables.
|
||||
*
|
||||
* @param listener listener to add
|
||||
* @return Listener handle
|
||||
*/
|
||||
public int addSubTableListener(SubTableListener listener) {
|
||||
final int prefixLen = m_path.length() + 1;
|
||||
final NetworkTable parent = this;
|
||||
|
||||
return m_inst.addListener(
|
||||
m_topicSub,
|
||||
EnumSet.of(NetworkTableEvent.Kind.kPublish, NetworkTableEvent.Kind.kImmediate),
|
||||
new Consumer<NetworkTableEvent>() {
|
||||
final Set<String> m_notifiedTables = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void accept(NetworkTableEvent event) {
|
||||
if (event.topicInfo == null) {
|
||||
return; // should not happen
|
||||
}
|
||||
String relativeKey = event.topicInfo.name.substring(prefixLen);
|
||||
int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
|
||||
if (endSubTable == -1) {
|
||||
return;
|
||||
}
|
||||
String subTableKey = relativeKey.substring(0, endSubTable);
|
||||
if (m_notifiedTables.contains(subTableKey)) {
|
||||
return;
|
||||
}
|
||||
m_notifiedTables.add(subTableKey);
|
||||
listener.tableCreated(parent, subTableKey, parent.getSubTable(subTableKey));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener.
|
||||
*
|
||||
* @param listener listener handle
|
||||
*/
|
||||
public void removeListener(int listener) {
|
||||
m_inst.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == this) {
|
||||
@@ -461,4 +583,8 @@ public final class NetworkTable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(m_inst, m_path);
|
||||
}
|
||||
|
||||
void close() {
|
||||
m_topicSub.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ public class PubSubOption {
|
||||
private static final int kTopicsOnly = 3;
|
||||
private static final int kPollStorage = 4;
|
||||
private static final int kKeepDuplicates = 5;
|
||||
private static final int kLocalRemote = 6;
|
||||
private static final int kExcludePub = 7;
|
||||
private static final int kExcludeSelf = 8;
|
||||
|
||||
PubSubOption(int type, double value) {
|
||||
PubSubOption(int type, int value) {
|
||||
m_type = type;
|
||||
m_value = value;
|
||||
}
|
||||
@@ -27,7 +30,7 @@ public class PubSubOption {
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption periodic(double period) {
|
||||
return new PubSubOption(kPeriodic, period);
|
||||
return new PubSubOption(kPeriodic, (int) (period * 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +41,7 @@ public class PubSubOption {
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption sendAll(boolean enabled) {
|
||||
return new PubSubOption(kSendAll, enabled ? 1.0 : 0.0);
|
||||
return new PubSubOption(kSendAll, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +51,7 @@ public class PubSubOption {
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption topicsOnly(boolean enabled) {
|
||||
return new PubSubOption(kTopicsOnly, enabled ? 1.0 : 0.0);
|
||||
return new PubSubOption(kTopicsOnly, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,13 +62,13 @@ public class PubSubOption {
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption keepDuplicates(boolean enabled) {
|
||||
return new PubSubOption(kKeepDuplicates, enabled ? 1.0 : 0.0);
|
||||
return new PubSubOption(kKeepDuplicates, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Polling storage for subscription. Specifies the maximum number of updates NetworkTables should
|
||||
* store between calls to the subscriber's poll() function. Defaults to 1 if sendAll is false, 20
|
||||
* if sendAll is true.
|
||||
* store between calls to the subscriber's readQueue() function. Defaults to 1 if sendAll is
|
||||
* false, 20 if sendAll is true.
|
||||
*
|
||||
* @param depth number of entries to save for polling.
|
||||
* @return option
|
||||
@@ -74,6 +77,69 @@ public class PubSubOption {
|
||||
return new PubSubOption(kPollStorage, depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* If only local value updates should be queued for readQueue(). See also remoteOnly() and
|
||||
* allUpdates(). Default is allUpdates. Only has an effect on subscriptions.
|
||||
*
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption localOnly() {
|
||||
return new PubSubOption(kLocalRemote, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* If only remote value updates should be queued for readQueue(). See also localOnly() and
|
||||
* allUpdates(). Default is allUpdates. Only has an effect on subscriptions.
|
||||
*
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption remoteOnly() {
|
||||
return new PubSubOption(kLocalRemote, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* If both local and remote value updates should be queued for readQueue(). See also localOnly()
|
||||
* and remoteOnly(). Default is allUpdates. Only has an effect on subscriptions.
|
||||
*
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption allUpdates() {
|
||||
return new PubSubOption(kLocalRemote, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't queue value updates for the given publisher. Only has an effect on subscriptions. Only
|
||||
* one exclusion may be set.
|
||||
*
|
||||
* @param publisher publisher handle to exclude
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption excludePublisher(int publisher) {
|
||||
return new PubSubOption(kExcludePub, publisher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't queue value updates for the given publisher. Only has an effect on subscriptions. Only
|
||||
* one exclusion may be set.
|
||||
*
|
||||
* @param publisher publisher to exclude
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption excludePublisher(Publisher publisher) {
|
||||
return new PubSubOption(kExcludePub, publisher != null ? publisher.getHandle() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't queue value updates for the internal publisher for an entry. Only has an effect on
|
||||
* entries.
|
||||
*
|
||||
* @param enabled True to enable, false to disable
|
||||
* @return option
|
||||
*/
|
||||
public static PubSubOption excludeSelf(boolean enabled) {
|
||||
return new PubSubOption(kExcludeSelf, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
final int m_type;
|
||||
final double m_value;
|
||||
final int m_value;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,15 @@ static constexpr size_t kMaxListeners = 512;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr bool IsSpecial(std::string_view name) {
|
||||
return name.empty() ? false : name.front() == '$';
|
||||
}
|
||||
|
||||
static constexpr bool PrefixMatch(std::string_view name,
|
||||
std::string_view prefix, bool special) {
|
||||
return (!special || !prefix.empty()) && wpi::starts_with(name, prefix);
|
||||
}
|
||||
|
||||
// Utility wrapper for making a set-like vector
|
||||
template <typename T>
|
||||
class VectorSet : public std::vector<T> {
|
||||
@@ -66,7 +75,7 @@ struct TopicData {
|
||||
static constexpr auto kType = Handle::kTopic;
|
||||
|
||||
TopicData(NT_Topic handle, std::string_view name)
|
||||
: handle{handle}, name{name} {}
|
||||
: handle{handle}, name{name}, special{IsSpecial(name)} {}
|
||||
|
||||
bool Exists() const { return onNetwork || !localPublishers.empty(); }
|
||||
|
||||
@@ -75,9 +84,10 @@ struct TopicData {
|
||||
// invariants
|
||||
wpi::SignalObject<NT_Topic> handle;
|
||||
std::string name;
|
||||
bool special;
|
||||
|
||||
Value lastValue; // also stores timestamp
|
||||
bool lastValueNetwork{false};
|
||||
Value lastValueNetwork;
|
||||
NT_Type type{NT_UNASSIGNED};
|
||||
std::string typeStr;
|
||||
unsigned int flags{0}; // for NT3 APIs
|
||||
@@ -179,6 +189,8 @@ struct MultiSubscriberData {
|
||||
}
|
||||
}
|
||||
|
||||
bool Matches(std::string_view name, bool special);
|
||||
|
||||
// invariants
|
||||
wpi::SignalObject<NT_MultiSubscriber> handle;
|
||||
std::vector<std::string> prefixes;
|
||||
@@ -188,6 +200,15 @@ struct MultiSubscriberData {
|
||||
VectorSet<NT_Listener> valueListeners;
|
||||
};
|
||||
|
||||
bool MultiSubscriberData::Matches(std::string_view name, bool special) {
|
||||
for (auto&& prefix : prefixes) {
|
||||
if (PrefixMatch(name, prefix, special)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ListenerData {
|
||||
ListenerData(NT_Listener handle, SubscriberData* subscriber,
|
||||
unsigned int eventMask, bool subscriberOwned)
|
||||
@@ -261,8 +282,9 @@ struct LSImpl {
|
||||
void CheckReset(TopicData* topic);
|
||||
|
||||
bool SetValue(TopicData* topic, const Value& value, unsigned int eventFlags,
|
||||
bool isDuplicate);
|
||||
void NotifyValue(TopicData* topic, unsigned int eventFlags, bool isDuplicate);
|
||||
bool isDuplicate, const PublisherData* publisher);
|
||||
void NotifyValue(TopicData* topic, unsigned int eventFlags, bool isDuplicate,
|
||||
const PublisherData* publisher);
|
||||
|
||||
void SetFlags(TopicData* topic, unsigned int flags);
|
||||
void SetPersistent(TopicData* topic, bool value);
|
||||
@@ -403,6 +425,7 @@ void SubscriberData::UpdateActive() {
|
||||
}
|
||||
|
||||
void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) {
|
||||
DEBUG4("NotifyTopic({}, {})\n", topic->name, eventFlags);
|
||||
auto topicInfo = topic->GetTopicInfo();
|
||||
if (!topic->listeners.empty()) {
|
||||
m_listenerStorage.Notify(topic->listeners, eventFlags, topicInfo);
|
||||
@@ -410,12 +433,9 @@ void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) {
|
||||
|
||||
wpi::SmallVector<NT_Listener, 32> listeners;
|
||||
for (auto listener : m_topicPrefixListeners) {
|
||||
if (listener->multiSubscriber) {
|
||||
for (auto&& prefix : listener->multiSubscriber->prefixes) {
|
||||
if (wpi::starts_with(topic->name, prefix)) {
|
||||
listeners.emplace_back(listener->handle);
|
||||
}
|
||||
}
|
||||
if (listener->multiSubscriber &&
|
||||
listener->multiSubscriber->Matches(topic->name, topic->special)) {
|
||||
listeners.emplace_back(listener->handle);
|
||||
}
|
||||
}
|
||||
if (!listeners.empty()) {
|
||||
@@ -461,7 +481,7 @@ void LSImpl::CheckReset(TopicData* topic) {
|
||||
return;
|
||||
}
|
||||
topic->lastValue = {};
|
||||
topic->lastValueNetwork = false;
|
||||
topic->lastValueNetwork = {};
|
||||
topic->type = NT_UNASSIGNED;
|
||||
topic->typeStr.clear();
|
||||
topic->flags = 0;
|
||||
@@ -470,18 +490,18 @@ void LSImpl::CheckReset(TopicData* topic) {
|
||||
}
|
||||
|
||||
bool LSImpl::SetValue(TopicData* topic, const Value& value,
|
||||
unsigned int eventFlags, bool isDuplicate) {
|
||||
unsigned int eventFlags, bool isDuplicate,
|
||||
const PublisherData* publisher) {
|
||||
DEBUG4("SetValue({}, {}, {}, {})", topic->name, value.time(), eventFlags,
|
||||
isDuplicate);
|
||||
if (topic->type != NT_UNASSIGNED && topic->type != value.type()) {
|
||||
return false;
|
||||
}
|
||||
bool isNetwork = (eventFlags & NT_EVENT_VALUE_REMOTE) != 0;
|
||||
if (!topic->lastValue || topic->lastValueNetwork == isNetwork ||
|
||||
value.time() >= topic->lastValue.time()) {
|
||||
if (!topic->lastValue || value.time() >= topic->lastValue.time()) {
|
||||
// TODO: notify option even if older value
|
||||
topic->type = value.type();
|
||||
topic->lastValue = value;
|
||||
topic->lastValueNetwork = isNetwork;
|
||||
NotifyValue(topic, eventFlags, isDuplicate);
|
||||
NotifyValue(topic, eventFlags, isDuplicate, publisher);
|
||||
}
|
||||
if (!isDuplicate && topic->datalogType == value.type()) {
|
||||
for (auto&& datalog : topic->datalogs) {
|
||||
@@ -492,10 +512,15 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
|
||||
void LSImpl::NotifyValue(TopicData* topic, unsigned int eventFlags,
|
||||
bool isDuplicate) {
|
||||
bool isDuplicate, const PublisherData* publisher) {
|
||||
bool isNetwork = (eventFlags & NT_EVENT_VALUE_REMOTE) != 0;
|
||||
for (auto&& subscriber : topic->localSubscribers) {
|
||||
if (subscriber->active &&
|
||||
(subscriber->config.keepDuplicates || !isDuplicate)) {
|
||||
(subscriber->config.keepDuplicates || !isDuplicate) &&
|
||||
((isNetwork && subscriber->config.fromRemote) ||
|
||||
(!isNetwork && subscriber->config.fromLocal)) &&
|
||||
(!publisher ||
|
||||
(publisher && (subscriber->config.excludePub != publisher->handle)))) {
|
||||
subscriber->pollStorage.emplace_back(topic->lastValue);
|
||||
subscriber->handle.Set();
|
||||
if (!subscriber->valueListeners.empty()) {
|
||||
@@ -872,7 +897,7 @@ MultiSubscriberData* LSImpl::AddMultiSubscriber(
|
||||
// subscribe to any already existing topics
|
||||
for (auto&& topic : m_topics) {
|
||||
for (auto&& prefix : prefixes) {
|
||||
if (wpi::starts_with(topic->name, prefix)) {
|
||||
if (PrefixMatch(topic->name, prefix, topic->special)) {
|
||||
topic->multiSubscribers.Add(subscriber);
|
||||
break;
|
||||
}
|
||||
@@ -993,10 +1018,8 @@ void LSImpl::AddListenerImpl(NT_Listener listenerHandle,
|
||||
if ((eventMask & NT_EVENT_IMMEDIATE) != 0 &&
|
||||
(eventMask & (NT_EVENT_PUBLISH | NT_EVENT_VALUE_ALL)) != 0) {
|
||||
for (auto&& topic : m_topics) {
|
||||
for (auto&& prefix : subscriber->prefixes) {
|
||||
if (wpi::starts_with(topic->name, prefix) && topic->Exists()) {
|
||||
topics.emplace_back(topic.get());
|
||||
}
|
||||
if (topic->Exists() && subscriber->Matches(topic->name, topic->special)) {
|
||||
topics.emplace_back(topic.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1122,11 +1145,8 @@ TopicData* LSImpl::GetOrCreateTopic(std::string_view name) {
|
||||
topic = m_topics.Add(m_inst, name);
|
||||
// attach multi-subscribers
|
||||
for (auto&& sub : m_multiSubscribers) {
|
||||
for (auto&& prefix : sub->prefixes) {
|
||||
if (wpi::starts_with(name, prefix)) {
|
||||
topic->multiSubscribers.Add(sub.get());
|
||||
break;
|
||||
}
|
||||
if (sub->Matches(name, topic->special)) {
|
||||
topic->multiSubscribers.Add(sub.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1218,16 +1238,20 @@ bool LSImpl::PublishLocalValue(PublisherData* publisher, const Value& value,
|
||||
return false;
|
||||
}
|
||||
if (publisher->active) {
|
||||
bool isDuplicate;
|
||||
bool isDuplicate, isNetworkDuplicate;
|
||||
if (force || publisher->config.keepDuplicates) {
|
||||
isDuplicate = false;
|
||||
isNetworkDuplicate = false;
|
||||
} else {
|
||||
isDuplicate = (publisher->topic->lastValue == value);
|
||||
isNetworkDuplicate = (publisher->topic->lastValueNetwork == value);
|
||||
}
|
||||
if (!isDuplicate && m_network) {
|
||||
if (!isNetworkDuplicate && m_network) {
|
||||
publisher->topic->lastValueNetwork = value;
|
||||
m_network->SetValue(publisher->handle, value);
|
||||
}
|
||||
return SetValue(publisher->topic, value, NT_EVENT_VALUE_LOCAL, isDuplicate);
|
||||
return SetValue(publisher->topic, value, NT_EVENT_VALUE_LOCAL, isDuplicate,
|
||||
publisher);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1241,6 +1265,9 @@ bool LSImpl::SetEntryValue(NT_Handle pubentryHandle, const Value& value) {
|
||||
if (!publisher) {
|
||||
if (auto entry = m_entries.Get(pubentryHandle)) {
|
||||
publisher = PublishEntry(entry, value.type());
|
||||
if (entry->subscriber->config.excludeSelf) {
|
||||
entry->subscriber->config.excludePub = publisher->handle;
|
||||
}
|
||||
}
|
||||
if (!publisher) {
|
||||
return false;
|
||||
@@ -1346,8 +1373,10 @@ 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,
|
||||
value == topic->lastValue);
|
||||
if (m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE,
|
||||
value == topic->lastValue, nullptr)) {
|
||||
topic->lastValueNetwork = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,17 +245,12 @@ void ServerConnection4::ProcessWsUpgrade() {
|
||||
m_websocket->open.connect([this, name = std::string{name}](std::string_view) {
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(*m_websocket);
|
||||
// TODO: set local flag appropriately
|
||||
m_clientId = m_server.m_serverImpl.AddClient(
|
||||
std::string dedupName;
|
||||
std::tie(dedupName, m_clientId) = m_server.m_serverImpl.AddClient(
|
||||
name, m_connInfo, false, *m_wire,
|
||||
[this](uint32_t repeatMs) { UpdatePeriodicTimer(repeatMs); });
|
||||
if (m_clientId < 0) {
|
||||
INFO("duplicate connection name '{}' (from {}), closing", name,
|
||||
m_connInfo);
|
||||
m_websocket->Fail(409, fmt::format("duplicate name '{}'", name));
|
||||
return;
|
||||
}
|
||||
INFO("CONNECTED NT4 client '{}' (from {})", name, m_connInfo);
|
||||
m_info.remote_id = name;
|
||||
INFO("CONNECTED NT4 client '{}' (from {})", dedupName, m_connInfo);
|
||||
m_info.remote_id = dedupName;
|
||||
m_server.AddConnection(this, m_info);
|
||||
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
|
||||
INFO("DISCONNECTED NT4 client '{}' (from {}): {}", m_info.remote_id,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "PubSubOptions.h"
|
||||
|
||||
#include "ntcore_c.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
using namespace nt;
|
||||
@@ -12,7 +13,7 @@ nt::PubSubOptions::PubSubOptions(std::span<const PubSubOption> options) {
|
||||
for (auto&& option : options) {
|
||||
switch (option.type) {
|
||||
case NT_PUBSUB_PERIODIC:
|
||||
periodic = option.value;
|
||||
periodicMs = option.value;
|
||||
break;
|
||||
case NT_PUBSUB_SENDALL:
|
||||
sendAll = option.value != 0;
|
||||
@@ -29,6 +30,31 @@ nt::PubSubOptions::PubSubOptions(std::span<const PubSubOption> options) {
|
||||
case NT_PUBSUB_POLLSTORAGE:
|
||||
pollStorageSize = static_cast<size_t>(option.value);
|
||||
break;
|
||||
case NT_PUBSUB_LOCALREMOTE:
|
||||
switch (static_cast<int>(option.value)) {
|
||||
case 0:
|
||||
case 3:
|
||||
fromLocal = true;
|
||||
fromRemote = true;
|
||||
break;
|
||||
case 1:
|
||||
fromLocal = true;
|
||||
fromRemote = false;
|
||||
break;
|
||||
case 2:
|
||||
fromLocal = false;
|
||||
fromRemote = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NT_PUBSUB_EXCLUDEPUB:
|
||||
excludePub = option.value;
|
||||
break;
|
||||
case NT_PUBSUB_EXCLUDESELF:
|
||||
excludeSelf = option.value != 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,18 @@ class PubSubOptions {
|
||||
PubSubOptions() = default;
|
||||
explicit PubSubOptions(std::span<const PubSubOption> options);
|
||||
|
||||
double periodic = 0.1;
|
||||
static constexpr unsigned int kDefaultPeriodicMs = 100;
|
||||
|
||||
unsigned int periodicMs = kDefaultPeriodicMs;
|
||||
size_t pollStorageSize = 1;
|
||||
bool sendAll = false;
|
||||
bool topicsOnly = false;
|
||||
bool prefixMatch = false;
|
||||
bool keepDuplicates = false;
|
||||
bool fromRemote = true;
|
||||
bool fromLocal = true;
|
||||
unsigned int excludePub = 0;
|
||||
bool excludeSelf = false;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -118,10 +118,10 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||
//
|
||||
|
||||
static std::span<const nt::PubSubOption> FromJavaPubSubOptions(
|
||||
JNIEnv* env, jintArray optionTypes, jdoubleArray optionValues,
|
||||
JNIEnv* env, jintArray optionTypes, jintArray optionValues,
|
||||
wpi::SmallVectorImpl<nt::PubSubOption>& arr) {
|
||||
JIntArrayRef types{env, optionTypes};
|
||||
JDoubleArrayRef values{env, optionValues};
|
||||
JIntArrayRef values{env, optionValues};
|
||||
if (types.size() != values.size()) {
|
||||
return {};
|
||||
}
|
||||
@@ -720,12 +720,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setTopicProperties
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: subscribe
|
||||
* Signature: (IILjava/lang/String;[I[D)I
|
||||
* Signature: (IILjava/lang/String;[I[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_subscribe
|
||||
(JNIEnv* env, jclass, jint topic, jint type, jstring typeStr,
|
||||
jintArray optionTypes, jdoubleArray optionValues)
|
||||
jintArray optionTypes, jintArray optionValues)
|
||||
{
|
||||
wpi::SmallVector<nt::PubSubOption, 4> options;
|
||||
return nt::Subscribe(
|
||||
@@ -748,12 +748,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_unsubscribe
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: publish
|
||||
* Signature: (IILjava/lang/String;[I[D)I
|
||||
* Signature: (IILjava/lang/String;[I[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_publish
|
||||
(JNIEnv* env, jclass, jint topic, jint type, jstring typeStr,
|
||||
jintArray optionTypes, jdoubleArray optionValues)
|
||||
jintArray optionTypes, jintArray optionValues)
|
||||
{
|
||||
wpi::SmallVector<nt::PubSubOption, 4> options;
|
||||
return nt::Publish(
|
||||
@@ -764,12 +764,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_publish
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: publishEx
|
||||
* Signature: (IILjava/lang/String;Ljava/lang/String;[I[D)I
|
||||
* Signature: (IILjava/lang/String;Ljava/lang/String;[I[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_publishEx
|
||||
(JNIEnv* env, jclass, jint topic, jint type, jstring typeStr,
|
||||
jstring properties, jintArray optionTypes, jdoubleArray optionValues)
|
||||
jstring properties, jintArray optionTypes, jintArray optionValues)
|
||||
{
|
||||
wpi::json j;
|
||||
try {
|
||||
@@ -804,12 +804,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_unpublish
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: getEntry
|
||||
* Signature: (IILjava/lang/String;[I[D)I
|
||||
* Signature: (IILjava/lang/String;[I[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntry__IILjava_lang_String_2_3I_3D
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntry__IILjava_lang_String_2_3I_3I
|
||||
(JNIEnv* env, jclass, jint topic, jint type, jstring typeStr,
|
||||
jintArray optionTypes, jdoubleArray optionValues)
|
||||
jintArray optionTypes, jintArray optionValues)
|
||||
{
|
||||
wpi::SmallVector<nt::PubSubOption, 4> options;
|
||||
return nt::GetEntry(
|
||||
@@ -856,12 +856,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_getTopicFromHandle
|
||||
/*
|
||||
* Class: edu_wpi_first_networktables_NetworkTablesJNI
|
||||
* Method: subscribeMultiple
|
||||
* Signature: (I[Ljava/lang/Object;[I[D)I
|
||||
* Signature: (I[Ljava/lang/Object;[I[I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_networktables_NetworkTablesJNI_subscribeMultiple
|
||||
(JNIEnv* env, jclass, jint inst, jobjectArray prefixes, jintArray optionTypes,
|
||||
jdoubleArray optionValues)
|
||||
jintArray optionValues)
|
||||
{
|
||||
if (!prefixes) {
|
||||
nullPointerEx.Throw(env, "prefixes cannot be null");
|
||||
|
||||
@@ -43,7 +43,6 @@ struct PublisherData {
|
||||
// 10 ms
|
||||
uint32_t periodMs;
|
||||
uint64_t nextSendMs{0};
|
||||
Value lastValue; // only used for duplicate value checking
|
||||
std::vector<Value> outValues; // outgoing values
|
||||
};
|
||||
|
||||
@@ -294,7 +293,7 @@ void CImpl::Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
|
||||
}
|
||||
publisher->handle = pubHandle;
|
||||
publisher->options = options;
|
||||
publisher->periodMs = std::lround(options.periodic * 100) * 10;
|
||||
publisher->periodMs = std::lround(options.periodicMs / 10.0) * 10;
|
||||
if (publisher->periodMs < kMinPeriodMs) {
|
||||
publisher->periodMs = kMinPeriodMs;
|
||||
}
|
||||
@@ -355,12 +354,6 @@ void CImpl::SetValue(NT_Publisher pubHandle, const Value& value) {
|
||||
return;
|
||||
}
|
||||
auto& publisher = *m_publishers[index];
|
||||
if (!publisher.options.keepDuplicates) {
|
||||
if (value == publisher.lastValue) {
|
||||
return;
|
||||
}
|
||||
publisher.lastValue = value;
|
||||
}
|
||||
if (publisher.outValues.empty() || publisher.options.sendAll) {
|
||||
publisher.outValues.emplace_back(value);
|
||||
} else {
|
||||
@@ -476,7 +469,6 @@ void ClientStartup::SetValue(NT_Publisher pubHandle, const Value& value) {
|
||||
assert(index < m_client.m_impl->m_publishers.size() &&
|
||||
m_client.m_impl->m_publishers[index]);
|
||||
auto& publisher = *m_client.m_impl->m_publishers[index];
|
||||
publisher.lastValue = value;
|
||||
// only send time 0 values until we have a RTT
|
||||
if (value.server_time() == 0) {
|
||||
WireEncodeBinary(m_binaryWriter.Add(), index, 0, value);
|
||||
|
||||
@@ -78,10 +78,12 @@ class SImpl;
|
||||
|
||||
class ClientData {
|
||||
public:
|
||||
ClientData(std::string_view name, std::string_view connInfo, bool local,
|
||||
ClientData(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: m_name{name},
|
||||
: m_originalName{originalName},
|
||||
m_name{name},
|
||||
m_connInfo{connInfo},
|
||||
m_local{local},
|
||||
m_setPeriodic{std::move(setPeriodic)},
|
||||
@@ -108,13 +110,16 @@ class ClientData {
|
||||
void UpdateMetaClientPub();
|
||||
void UpdateMetaClientSub();
|
||||
|
||||
// returns nullptr if there is no subscriber for that topic name
|
||||
SubscriberData* GetSubscriber(std::string_view name, bool special);
|
||||
std::span<SubscriberData*> GetSubscribers(
|
||||
std::string_view name, bool special,
|
||||
wpi::SmallVectorImpl<SubscriberData*>& buf);
|
||||
|
||||
std::string_view GetOriginalName() const { return m_originalName; }
|
||||
std::string_view GetName() const { return m_name; }
|
||||
int GetId() const { return m_id; }
|
||||
|
||||
protected:
|
||||
std::string m_originalName;
|
||||
std::string m_name;
|
||||
std::string m_connInfo;
|
||||
bool m_local; // local to machine
|
||||
@@ -138,10 +143,12 @@ class ClientData {
|
||||
|
||||
class ClientData4Base : public ClientData, protected ClientMessageHandler {
|
||||
public:
|
||||
ClientData4Base(std::string_view name, std::string_view connInfo, bool local,
|
||||
ClientData4Base(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server,
|
||||
int id, wpi::Logger& logger)
|
||||
: ClientData{name, connInfo, local, setPeriodic, server, id, logger} {}
|
||||
: ClientData{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger} {}
|
||||
|
||||
protected:
|
||||
// ClientMessageHandler interface
|
||||
@@ -165,7 +172,8 @@ class ClientDataLocal final : public ClientData4Base {
|
||||
|
||||
public:
|
||||
ClientDataLocal(SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{"", "", true, [](uint32_t) {}, server, id, logger} {}
|
||||
: ClientData4Base{"", "", "", true, [](uint32_t) {}, server, id, logger} {
|
||||
}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final {}
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data) final {}
|
||||
@@ -183,10 +191,12 @@ class ClientDataLocal final : public ClientData4Base {
|
||||
|
||||
class ClientData4 final : public ClientData4Base {
|
||||
public:
|
||||
ClientData4(std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic,
|
||||
SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{name, connInfo, local, setPeriodic, server, id, logger},
|
||||
ClientData4(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local, WireConnection& wire,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData4Base{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger},
|
||||
m_wire{wire} {}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final;
|
||||
@@ -239,7 +249,7 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireConnection3& wire, ServerImpl::Connected3Func connected,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData{"", connInfo, local, setPeriodic, server, id, logger},
|
||||
: ClientData{"", "", connInfo, local, setPeriodic, server, id, logger},
|
||||
m_connected{std::move(connected)},
|
||||
m_wire{wire},
|
||||
m_decoder{*this} {}
|
||||
@@ -357,7 +367,7 @@ struct SubscriberData {
|
||||
topicNames{topicNames.begin(), topicNames.end()},
|
||||
subuid{subuid},
|
||||
options{options},
|
||||
periodMs(std::lround(options.periodic * 100) * 10) {
|
||||
periodMs(std::lround(options.periodicMs / 10.0) * 10) {
|
||||
if (periodMs < kMinPeriodMs) {
|
||||
periodMs = kMinPeriodMs;
|
||||
}
|
||||
@@ -367,7 +377,7 @@ struct SubscriberData {
|
||||
const PubSubOptions& options_) {
|
||||
topicNames = {topicNames_.begin(), topicNames_.end()};
|
||||
options = options_;
|
||||
periodMs = std::lround(options_.periodic * 100) * 10;
|
||||
periodMs = std::lround(options_.periodicMs / 10.0) * 10;
|
||||
if (periodMs < kMinPeriodMs) {
|
||||
periodMs = kMinPeriodMs;
|
||||
}
|
||||
@@ -403,8 +413,9 @@ class SImpl {
|
||||
TopicData* m_metaClients;
|
||||
|
||||
// ServerImpl interface
|
||||
int AddClient(std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic);
|
||||
std::pair<std::string, int> AddClient(
|
||||
std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic);
|
||||
int AddClient3(std::string_view connInfo, bool local,
|
||||
net3::WireConnection3& wire,
|
||||
ServerImpl::Connected3Func connected,
|
||||
@@ -454,7 +465,8 @@ struct Writer : public mpack_writer_t {
|
||||
|
||||
static void WriteOptions(mpack_writer_t& w, const PubSubOptions& options) {
|
||||
int size = (options.sendAll ? 1 : 0) + (options.topicsOnly ? 1 : 0) +
|
||||
(options.periodic != 0.1 ? 1 : 0) + (options.prefixMatch ? 1 : 0);
|
||||
(options.periodicMs != PubSubOptions::kDefaultPeriodicMs ? 1 : 0) +
|
||||
(options.prefixMatch ? 1 : 0);
|
||||
mpack_start_map(&w, size);
|
||||
if (options.sendAll) {
|
||||
mpack_write_str(&w, "all");
|
||||
@@ -464,9 +476,9 @@ static void WriteOptions(mpack_writer_t& w, const PubSubOptions& options) {
|
||||
mpack_write_str(&w, "topicsonly");
|
||||
mpack_write_bool(&w, true);
|
||||
}
|
||||
if (options.periodic != 0.1) {
|
||||
if (options.periodicMs != PubSubOptions::kDefaultPeriodicMs) {
|
||||
mpack_write_str(&w, "periodic");
|
||||
mpack_write_float(&w, options.periodic);
|
||||
mpack_write_float(&w, options.periodicMs / 1000.0);
|
||||
}
|
||||
if (options.prefixMatch) {
|
||||
mpack_write_str(&w, "prefix");
|
||||
@@ -521,14 +533,17 @@ void ClientData::UpdateMetaClientSub() {
|
||||
}
|
||||
}
|
||||
|
||||
SubscriberData* ClientData::GetSubscriber(std::string_view name, bool special) {
|
||||
std::span<SubscriberData*> ClientData::GetSubscribers(
|
||||
std::string_view name, bool special,
|
||||
wpi::SmallVectorImpl<SubscriberData*>& buf) {
|
||||
buf.resize(0);
|
||||
for (auto&& subPair : m_subscribers) {
|
||||
SubscriberData* subscriber = subPair.getSecond().get();
|
||||
if (subscriber->Matches(name, special)) {
|
||||
return subscriber;
|
||||
buf.emplace_back(subscriber);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
void ClientData4Base::ClientPublish(int64_t pubuid, std::string_view name,
|
||||
@@ -1189,11 +1204,8 @@ void ClientData3::ClientHello(std::string_view self_id,
|
||||
fmt::format("unsupported protocol version {:04x}", proto_rev));
|
||||
return;
|
||||
}
|
||||
m_name = self_id;
|
||||
// create a unique name if none provided
|
||||
if (m_name.empty()) {
|
||||
m_name = fmt::format("NT3@{}", m_connInfo);
|
||||
}
|
||||
// create a unique name (just ignore provided client id)
|
||||
m_name = fmt::format("NT3@{}", m_connInfo);
|
||||
m_connected(m_name, 0x0300);
|
||||
m_connected = nullptr; // no longer required
|
||||
|
||||
@@ -1487,16 +1499,22 @@ SImpl::SImpl(wpi::Logger& logger) : m_logger{logger} {
|
||||
m_localClient = static_cast<ClientDataLocal*>(m_clients.back().get());
|
||||
}
|
||||
|
||||
int SImpl::AddClient(std::string_view name, std::string_view connInfo,
|
||||
bool local, WireConnection& wire,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic) {
|
||||
std::pair<std::string, int> SImpl::AddClient(
|
||||
std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic) {
|
||||
// strip anything after @ in the name
|
||||
name = wpi::split(name, '@').first;
|
||||
if (name.empty()) {
|
||||
name = "NT4";
|
||||
}
|
||||
size_t index = m_clients.size();
|
||||
// find an empty slot and ensure there's no duplicates
|
||||
// find an empty slot and check for duplicates
|
||||
// just do a linear search as number of clients is typically small (<10)
|
||||
int duplicateName = 0;
|
||||
for (size_t i = 0, end = index; i < end; ++i) {
|
||||
auto& clientData = m_clients[i];
|
||||
if (clientData && clientData->GetName() == name) {
|
||||
return -1; // don't allow duplicate client names
|
||||
if (clientData && clientData->GetOriginalName() == name) {
|
||||
++duplicateName;
|
||||
} else if (!clientData && index == end) {
|
||||
index = i;
|
||||
}
|
||||
@@ -1505,14 +1523,24 @@ int SImpl::AddClient(std::string_view name, std::string_view connInfo,
|
||||
m_clients.emplace_back();
|
||||
}
|
||||
|
||||
// if duplicate name, de-duplicate
|
||||
std::string dedupName;
|
||||
if (duplicateName > 0) {
|
||||
dedupName = fmt::format("{}@{}", name, duplicateName);
|
||||
} else {
|
||||
dedupName = name;
|
||||
}
|
||||
|
||||
auto& clientData = m_clients[index];
|
||||
clientData = std::make_unique<ClientData4>(name, connInfo, local, wire,
|
||||
std::move(setPeriodic), *this,
|
||||
index, m_logger);
|
||||
clientData = std::make_unique<ClientData4>(name, dedupName, connInfo, local,
|
||||
wire, std::move(setPeriodic),
|
||||
*this, index, m_logger);
|
||||
|
||||
// create client meta topics
|
||||
clientData->m_metaPub = CreateMetaTopic(fmt::format("$clientpub${}", name));
|
||||
clientData->m_metaSub = CreateMetaTopic(fmt::format("$clientsub${}", name));
|
||||
clientData->m_metaPub =
|
||||
CreateMetaTopic(fmt::format("$clientpub${}", dedupName));
|
||||
clientData->m_metaSub =
|
||||
CreateMetaTopic(fmt::format("$clientsub${}", dedupName));
|
||||
|
||||
// update meta topics
|
||||
clientData->UpdateMetaClientPub();
|
||||
@@ -1521,7 +1549,7 @@ int SImpl::AddClient(std::string_view name, std::string_view connInfo,
|
||||
wire.Flush();
|
||||
|
||||
DEBUG3("AddClient('{}', '{}') -> {}", name, connInfo, index);
|
||||
return index;
|
||||
return {std::move(dedupName), index};
|
||||
}
|
||||
|
||||
int SImpl::AddClient3(std::string_view connInfo, bool local,
|
||||
@@ -1532,8 +1560,9 @@ int SImpl::AddClient3(std::string_view connInfo, bool local,
|
||||
// find an empty slot; we can't check for duplicates until we get a hello.
|
||||
// just do a linear search as number of clients is typically small (<10)
|
||||
for (size_t i = 0, end = index; i < end; ++i) {
|
||||
if (!m_clients[i] && index == end) {
|
||||
if (!m_clients[i]) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == m_clients.size()) {
|
||||
@@ -1994,14 +2023,15 @@ TopicData* SImpl::CreateTopic(ClientData* client, std::string_view name,
|
||||
}
|
||||
|
||||
// look for subscriber matching prefixes
|
||||
bool hasSubscriber = false;
|
||||
if (auto subscriber = aClient->GetSubscriber(name, topic->special)) {
|
||||
wpi::SmallVector<SubscriberData*, 16> subscribersBuf;
|
||||
auto subscribers =
|
||||
aClient->GetSubscribers(name, topic->special, subscribersBuf);
|
||||
for (auto subscriber : subscribers) {
|
||||
topic->subscribers.Add(subscriber);
|
||||
hasSubscriber = true;
|
||||
}
|
||||
|
||||
// don't announce to this client if no subscribers
|
||||
if (!hasSubscriber) {
|
||||
if (subscribers.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2292,9 +2322,11 @@ void ServerImpl::ProcessIncomingBinary(int clientId,
|
||||
m_impl->m_clients[clientId]->ProcessIncomingBinary(data);
|
||||
}
|
||||
|
||||
int ServerImpl::AddClient(std::string_view name, std::string_view connInfo,
|
||||
bool local, WireConnection& wire,
|
||||
SetPeriodicFunc setPeriodic) {
|
||||
std::pair<std::string, int> ServerImpl::AddClient(std::string_view name,
|
||||
std::string_view connInfo,
|
||||
bool local,
|
||||
WireConnection& wire,
|
||||
SetPeriodicFunc setPeriodic) {
|
||||
return m_impl->AddClient(name, connInfo, local, wire, std::move(setPeriodic));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "NetworkInterface.h"
|
||||
@@ -53,8 +54,10 @@ class ServerImpl final {
|
||||
|
||||
// Returns -1 if cannot add client (e.g. due to duplicate name).
|
||||
// Caller must ensure WireConnection lifetime lasts until RemoveClient() call.
|
||||
int AddClient(std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, SetPeriodicFunc setPeriodic);
|
||||
std::pair<std::string, int> AddClient(std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
WireConnection& wire,
|
||||
SetPeriodicFunc setPeriodic);
|
||||
int AddClient3(std::string_view connInfo, bool local,
|
||||
net3::WireConnection3& wire, Connected3Func connected,
|
||||
SetPeriodicFunc setPeriodic);
|
||||
|
||||
@@ -238,10 +238,12 @@ static void WireDecodeTextImpl(std::string_view in, T& out,
|
||||
// periodic
|
||||
auto periodicIt = joptions->find("periodic");
|
||||
if (periodicIt != joptions->end()) {
|
||||
if (!GetNumber(periodicIt->second, &options.periodic)) {
|
||||
double val;
|
||||
if (!GetNumber(periodicIt->second, &val)) {
|
||||
error = "periodic value must be a number";
|
||||
goto err;
|
||||
}
|
||||
options.periodicMs = val * 1000;
|
||||
}
|
||||
|
||||
// send all changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user