Compare commits

...

88 Commits

Author SHA1 Message Date
Ryan Blue
5a52b51443 [hal] Add RobotController.getSerialNumber() (#4783) 2022-12-08 21:58:55 -08:00
Thad House
69a66ec5ec [wpilib] Fix multiple motor safety issues (#4784)
Java was missing the motor safety thread entirely
C++ accidentally used a manual reset event, causing the motor safety thread to spin.
C++ PWMMotorController would not feed the watch kitty.
Both languages would call feed() from the StopMotor call, causing some ping ponging.
2022-12-08 19:47:47 -08:00
sciencewhiz
989c9fb29a [wpimath] Revert Rotation2D change that limits angles (#4781)
Reverts "[wpimath] Constrain Rotation2d range to -pi to pi (#4611)"
This reverts commit d1d458db2b.

This broke multiple teams code in beta. It is also easier to limit the angle externally, then reconstruct a larger angle that got limited. This additionally adds comments to clarify the behavior and retains tests that were added in the reverted commit, and fixes a javadoc comment angle reference.
2022-12-08 18:10:44 -08:00
Peter Johnson
0f5b08ec69 [wpigui] Update imgui to 1.89.1+ (#4780) 2022-12-08 00:24:27 -08:00
Starlight220
fba191099c [examples] AddressableLED: Add unit test (#4779) 2022-12-07 21:47:47 -08:00
Peter Johnson
b390cad095 [wpilibj] Consistently use ErrorMessages.requireNonNullParam (#4776)
Also remove wpilibj version of ErrorMessages and consistently use static import.
2022-12-07 21:46:26 -08:00
Peter Johnson
b9772214d9 [wpilib] Sendable: Don't call setter for getter changes 2022-12-07 21:29:51 -08:00
Peter Johnson
342c375a71 [ntcore] Add subscriber option to exclude single publisher
This enables limited self-exclusion for entries seeing their own changes
or similarly with a pub/sub pair.
2022-12-07 21:29:51 -08:00
Peter Johnson
b0e4053087 [ntcore] Use int for options instead of double
Periodic time is stored in milliseconds.
2022-12-07 21:29:51 -08:00
Peter Johnson
f3e666b7bb [cscore] Convert YUYV and UYVY directly to grayscale (#4777)
This is significantly more efficient than converting by way of BGR.
2022-12-07 10:36:40 -08:00
William Toth
b300518bd1 [hal] Add CAN Stream API to Java through JNI bindings (#4193)
The CAN Stream API allows defining an buffer to receive an
arbitrary set of CAN messages, based on an ID and a mask. Messages
are added to this queue separate of other CAN APIs. This means the
messages can be receive without impacting other APIs such as
vendor APIs.

This enables things like detection of what devices are on the
bus, or custom decoding, without using vendor APIs.

Co-authored-by: Thad House <thadhouse1@gmail.com>
2022-12-06 21:58:09 -08:00
Peter Johnson
be27171236 [wpilibj] Shuffleboard: Check for null sendable (#4772)
Adding a null sendable to a container could result in a delayed NullPointerException.
2022-12-06 21:14:09 -08:00
Starlight220
4bbdbdfb48 [commands] Move GroupedCommands to CommandScheduler (#4728)
Move the command group checking functionality from CommandGroupBase into CommandScheduler.
Update references to grouping as composition for clarity (because explicitly grouping isn't the only way to do it).
Deprecate the static factory methods parallel, race, and deadline in CommandGroupBase in favor of the identical ones in Commands.
2022-12-06 21:13:31 -08:00
Tyler Veness
f18fd41ac3 [wpimath] Remove broken and obsoleted ComputerVisionUtil functions (#4775)
The ComputerVisionUtil class was added before AprilTag support was
announced. Now that it has, the functions for estimating a pose from
range and yaw are no longer needed; it's just better to get the pose
directly from the AprilTag.

The coordinate system on some function arguments was confusing or didn't
match the NWU convention the rest of the library uses. It's easier to
remove the functions now and add them back after they're fixed since the
fixes aren't trivial.

The range function was removed because it uses pitch and yaw in the
camera's spherical coordinate system, which is obsoleted by AprilTags.
AprilTags give you a 6DOF pose directly, so range can be obtained via
Pose2d.getTranslation().getDistance().

Fixes #4757.
2022-12-06 21:11:27 -08:00
Peter Johnson
2d0faecf4f [glass] DataSource: Add spinlock to protect value (#4771)
A lock is needed here due to HAL async callbacks.
2022-12-06 21:09:14 -08:00
Peter Johnson
348bd107fc [hal] Add CANManufacturer for The Thrifty Bot (#4773) 2022-12-06 21:08:45 -08:00
Starlight220
3149dc64b8 [examples] HatchbotInlined: Use Subsystem factories (#4765) 2022-12-06 15:10:39 -08:00
Jordan McMichael
8618dd4160 [glass, wpilib] Replace remaining references to Speed Controller with Motor Controller (#4769) 2022-12-05 20:06:43 -08:00
Peter Johnson
72e21a1ed1 [apriltag] Use wpilibsuite fork of apriltag (#4764) 2022-12-05 19:59:24 -08:00
Ryan Blue
eab0d929e6 [commands] CommandGenericHID POV methods: Fix docs (NFC) (#4760) 2022-12-05 13:46:46 -08:00
Ryan Blue
6789869663 [wpilib] Call set(0) rather than disable for stopMotor (#4763)
For PWM motor controllers, this is going to be the fastest way to stop the motor,
as disabling the PWM output will have a small delay due to the motor controller
needing to detect the missing signal.

Note Set(0) is not a safe approach for CAN motor controllers, which may have closed
loop operation, non-% output set() calls, etc.
2022-12-05 13:33:54 -08:00
Dustin Spicuzza
c9dea2968d [cscore] Emit warning that USB Camera isn't supported on OSX (#4766)
Reduces user confusion, as right now there's really no indication that something is wrong other than there's no camera,
2022-12-05 13:30:13 -08:00
sciencewhiz
8f402645f5 [commands] Fix PIDSubsystem setSetpoint behavior (#4759)
No longer stores a temporary setpoint in PIDSubsystem, instead
immediately sending to PIDController. This fixes an issue where the
setpoint didn't take effect until the Subsystem Periodic method ran, and
could cause commands to finish early if they were scheduled after the
subsystem periodic method ran because it used the old setpoint.
2022-12-03 11:32:46 -08:00
Tyler Veness
f24ad1d715 [build] Upgrade to googletest 1.12.1 (#4752)
This fixes GCC 12 warnings for googletest internals.
2022-12-03 11:32:08 -08:00
Jaci Brunning
ff88756864 [wpimath] Add new DCMotor functions for alternative calculations and reduction calculation (#4749) 2022-12-03 09:47:16 -08:00
superpenguin612
f58873db8e [wpimath] Remove extra terms in matrix for pose estimator docs (#4756) 2022-12-03 09:46:34 -08:00
superpenguin612
37e969b41a [wpimath] Add constructors to pose estimators with default standard deviations (#4754) 2022-12-03 09:46:10 -08:00
Tyler Veness
13cdc29382 [ci] Rename comment command from "/wpiformat" to "/format" (#4755)
It now runs more than just wpiformat.
2022-12-02 18:24:06 -08:00
Tyler Veness
6e23985ae6 [examples] Add main include directory to test builds (#4751)
This fixes the following compilation errors:
```
/home/tav/frc/wpilib/allwpilib/wpilibcExamples/src/main/cpp/examples/UnitTest/cpp/subsystems/Intake.cpp:5:10: fatal error: subsystems/Intake.h: No such file or directory
    5 | #include "subsystems/Intake.h"
      |          ^~~~~~~~~~~~~~~~~~~~~

/home/tav/frc/wpilib/allwpilib/wpilibcExamples/src/test/cpp/examples/UnitTest/cpp/subsystems/IntakeTest.cpp:11:10: fatal error: Constants.h: No such file or directory
   11 | #include "Constants.h"
      |          ^~~~~~~~~~~~~
```
2022-12-02 10:37:49 -08:00
Starlight220
66bb0ffb2c [examples] Add unit testing infrastructure (#4646) 2022-12-02 08:40:14 -08:00
Tyler Veness
74cc86c4c5 [wpimath] Make transform tests use pose/transform equality operators (#4675)
Also add more nonzeros to Transform3d tests to make them more
comprehensive.
2022-12-02 08:36:57 -08:00
Jordan McMichael
e22d8cc343 [wpimath] Use Odometry for internal state in Pose Estimation (#4668)
This effectively replaces the Unscented Kalman Filter used for Pose Estimation with the Odometry model, and uses a recalculable Kalman gain matrix to update pose estimations whenever a vision measurement is added.

Notably, this change removes the need for the confusing generics used in Java, and the C++ implementation got quite a bit less complex as well.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-12-02 08:36:10 -08:00
Ryan Blue
68dba92630 [ci] Update mac and windows builds to Java 17 (#4750) 2022-12-02 08:31:38 -08:00
Tyler Veness
23bfc2d9ab [sim] Remove unmaintained Gazebo support (#4736) 2022-12-01 20:46:47 -08:00
Starlight220
1f1461e254 [wpilib] Add method to enable/disable LiveWindow in test mode (#4678) 2022-12-01 13:28:06 -08:00
Tyler Veness
eae68fc165 [wpimath] Add tolerance for Rotation3d rotation matrix special orthogonality (#4744) 2022-11-30 19:51:31 -08:00
Tyler Veness
4c4545fb4b [apriltag] Suppress warning (#4743) 2022-11-30 19:51:18 -08:00
Tyler Veness
16ffaa754d [docs] Generate docs for apriltag subproject (#4745) 2022-11-30 19:50:52 -08:00
Thad House
5e74ff26d8 [apriltag, build] Update native utils, add apriltag impl and JNI (#4733)
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2022-11-30 00:16:29 -08:00
Dustin Spicuzza
53875419a1 [hal] Allow overriding stderr printing by HAL_SendError (#4742) 2022-11-29 23:19:15 -08:00
Peter Johnson
aa6499e920 [ntcore] Fix special topic multi-subscriber handling (#4740)
It now matches server behavior, where "" doesn't match special topics.

Also fixes duplicate notification that could occur in some cases.
2022-11-29 21:58:57 -08:00
Peter Johnson
df70351107 [build] Fix cmake install of thirdparty includes (#4741) 2022-11-29 21:58:38 -08:00
Peter Johnson
e9bd50ff9b [glass] NT view: clear meta-topic info on disconnect (#4732) 2022-11-29 21:57:48 -08:00
Peter Johnson
9b319fd56b [ntcore] Add sub option for local vs remote changes (#4731)
This is the subscriber readQueue version of the local value listener flag.
2022-11-29 21:57:20 -08:00
Peter Johnson
18d28ec5e3 [ntcore] Remove duplicate value checking from ClientImpl
This is no longer required with the duplicate value checking in
LocalStorage.
2022-11-29 09:49:47 -08:00
Peter Johnson
bdfb625211 [ntcore] Send duplicate values to network if necessary
Essentially this separates duplicate value detection between network and
local.
2022-11-29 09:49:47 -08:00
Ryan Blue
21003e34eb [commands] Update Subsystem factories and example to return CommandBase (#4729)
Also update rapidreactcommandbot example factories to fit this convention (as in #4655).

C++ does not need an update as CommandPtr already uses CommandBase (#4677).
2022-11-28 19:47:18 -08:00
Starlight220
70080457d5 [commands] Refactor ProxyScheduleCommand, SelectCommand into ProxyCommand (#4534) 2022-11-28 14:43:10 -08:00
Colin Wong
e82cd5147b [wpilib] Tweak Color HSV formula and use in AddressableLED (#4724)
The Color algorithm was tweaked to:
a) not produce incorrect values if the user happens to input a hue outside the [0, 180) range, and
b) more accurately convert the hue remainder from range 0-30 to 0-255. The current conversion vastly overshoots the multiplier (converts 0-30 to 0-270) and relies on clamping the value when constructing the Color object to produce a slightly incorrect result.
2022-11-28 14:42:22 -08:00
Colin Wong
ec124bb662 [commands] Allow unsetting a subsystem's default command (#4621) 2022-11-28 14:03:14 -08:00
Ryan Blue
2b2aa8eef7 [examples] Update all examples to use NWU coordinate conventions (#4725) 2022-11-28 13:49:49 -08:00
Starlight220
cb38bacfe8 [commands] Revert to original Trigger implementation (#4673)
Trigger was refactored to use BooleanEvent when it was introduced in #4104.
This reverts to the original implementation until edge-based BooleanEvents can be fixed.
2022-11-28 13:48:48 -08:00
Starlight220
15561338d5 [commands] Remove one more default command isFinished check (#4727) 2022-11-28 13:44:37 -08:00
Ryan Blue
ca35a2e097 Add simgui files to .gitignore (#4726) 2022-11-28 08:57:57 -08:00
Starlight220
20dbae0cee [examples] Renovate command-based examples (#4409)
Refactor some examples to use newer features, such as HID factories, library-provided command factories, CommandPtr (C++), as well as new idioms such as static/instance command factories.
2022-11-28 08:55:13 -08:00
Eli Barnett
1a59737f40 [commands] Add convenience factories (#4460)
Co-authored-by: Starlight220 <53231611+Starlight220@users.noreply.github.com>
2022-11-28 07:41:25 -08:00
Tyler Veness
42b6d4e3f7 Use defaulted comparison operators in C++ (#4723)
Comparison operators which compared against every class member variable
now use C++20's default comparison operators.

Also remove operator!= that in C++20 is now auto-generated from operator==.
2022-11-27 21:01:01 -08:00
Peter Johnson
135c13958f [wpigui] Add FontAwesome (#4713) 2022-11-27 20:00:17 -08:00
Peter Johnson
ffbfc61532 [ntcore] Add NetworkTable table-specific listeners (#4640)
These are similar, but not quite identical to, the NT3 NetworkTable
table listeners.

Also add table topic-only multi-subscriber to ensure functions like
getKeys() work properly regardless of other subscriptions.
2022-11-27 19:46:34 -08:00
Starlight220
8958b2a4da [commands] Add property tests for command compositions (#4715) 2022-11-27 16:23:56 -08:00
Starlight220
e4ac09077c [wpilib] Add link to MotorSafety article (#4720) 2022-11-27 16:23:06 -08:00
Starlight220
f40de0c120 [commands] Add C++ factory templates (#4686) 2022-11-27 11:27:44 -08:00
Peter Johnson
51fa3e851f [build] cmake: Use FetchContent instead of ExternalProject (#4714)
Also switch to using thirdparty-fonts instead of generating them.
2022-11-26 23:05:41 -08:00
Peter Johnson
1da84b2255 [wpigui] Reload fonts to scale rather than preloading (#4712) 2022-11-26 22:30:38 -08:00
Peter Johnson
e43e2fbc84 [wpiutil] StringExtras: Add UnescapeCString (#4707)
Based on implementation in glass but enhanced for generic use.
2022-11-26 18:21:45 -08:00
Peter Johnson
5804d8fa84 [ntcore] Server: Properly handle multiple subscribers (#4717)
Previously, only the first subscriber was actually matched to a topic
when a topic was created; this was a problem when later publishing
values as a client could have both a topic-only subscriber and a normal
subscriber, and only the first one would end up being subscribed to the
topic.
2022-11-26 17:02:22 -08:00
Peter Johnson
169ef5fabf [glass] Update NT view for topicsOnly and sendAll changes (#4718) 2022-11-26 17:01:40 -08:00
Starlight220
148759ef54 [examples] CANPDP: Expand properties shown (#4687) 2022-11-25 23:51:15 -08:00
Starlight220
58ed112b51 [commands] RepeatCommand: restart on following iteration (#4706)
This fixes InstantCommand.repeatedly().
2022-11-25 23:50:42 -08:00
Ryan Blue
dd1da77d20 [readme] Fix broken CI badge (#4710) 2022-11-25 23:49:47 -08:00
Ryan Blue
7cda85df20 [build] Check Gradle plugin repo last to fix CI (#4711) 2022-11-25 23:48:18 -08:00
Thad House
7ed9b13277 [build] Bump version plugin to fix null tag (#4705) 2022-11-24 22:10:59 -08:00
Tyler Veness
6b4f26225d [apriltag] Fix pluralization of apriltag artifacts (#4671) 2022-11-24 09:06:38 -08:00
Peter Johnson
b2d2924b72 [cscore] Add Y16 image support (#4702) 2022-11-24 09:06:06 -08:00
Peter Johnson
34ec89c041 [wpilibc] Shuffleboard SimpleWidget: Return pointer instead of reference (#4703)
Based on beta test feedback, returning a pointer is more intuitive, as
typically the return value is late bound to an instance variable.
2022-11-24 09:05:37 -08:00
Peter Johnson
e15200068d [ci] Disable HW testbench runs (#4704)
These are currently broken with no timetable to fix.
2022-11-24 09:04:57 -08:00
Starlight220
d5200db6cd [wpimath] Rename HolonomicDriveController.calculate params (#4683) 2022-11-23 23:13:50 -08:00
Tyler Veness
2ee3d86de4 [wpimath] Clarify Rotation3d roll-pitch-yaw direction (#4699) 2022-11-23 23:12:59 -08:00
Peter Johnson
9f0a8b930f [cscore] Use MFVideoFormat_L8 for Gray on Windows (#4701) 2022-11-23 22:15:56 -08:00
Peter Johnson
2bca43779e [cscore] Add UYVY image support (#4700) 2022-11-23 22:00:31 -08:00
ohowe
4307d0ee8b [glass] Plot: allow for more than 11 plots (#4685)
Since m_windows is sorted using the ascii, when "Plot <10>" is reached it will be before "Plot <2>" in `m_windows` which makes it so it will not add a new plot after the id 10 is reached. This also fixes a potential issue of someone manually changing an id in the file, which would break adding a new plot in some circumstances.
2022-11-23 13:57:29 -08:00
Starlight220
3fe8d355a1 [examples] StateSpaceDifferentialDriveSimulation: Use encoder reversed constants (#4682)
This matches the Java example.
2022-11-22 10:29:40 -08:00
Peter Johnson
b44034dadc [ntcore] Allow duplicate client IDs on server (#4676)
Currently, the server rejects duplicate client IDs. As we want to make
the client implementation as simple as possible, instead deduplicate the
name on the server side by appending "@" and a count.

NT4 spec has been updated for this change.
2022-11-22 10:27:49 -08:00
Starlight220
52d2c53888 [commands] Rename Java factory wait() to waitSeconds() (#4684)
This is needed to avoid a conflict with Object.wait() when using static imports.
C++ doesn't have this issue, and has units, so Wait() still makes sense there.
2022-11-22 10:16:27 -08:00
Thad House
76e918f71e [build] Fix JNI artifacts linking to incorrect libraries (#4680) 2022-11-21 20:33:46 -08:00
Starlight220
0bee875aff [commands] Change C++ CommandPtr to use CommandBase (#4677) 2022-11-21 09:45:50 -08:00
Peter Johnson
98e922313b [glass] Don't check IsConnected for NT widgets (#4674)
Checking this isn't required, and prevented these widgets from working
in the simulation GUI without another NT client connected.
2022-11-20 17:27:28 -08:00
amquake
9a36373b8f [apriltag] Switch 2022 apriltag layout length and width values (#4670) 2022-11-19 09:11:08 -08:00
550 changed files with 10461 additions and 10257 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -5,6 +5,10 @@ doxygen.log
build*/
!buildSrc/
simgui-ds.json
simgui-window.json
simgui.json
# Created by the jenkins test script
test-reports

View File

@@ -1,6 +1,6 @@
# WPILib Project
![CI](https://github.com/wpilibsuite/allwpilib/workflows/CI/badge.svg)
[![Gradle](https://github.com/wpilibsuite/allwpilib/actions/workflows/gradle.yml/badge.svg?branch=main)](https://github.com/wpilibsuite/allwpilib/actions/workflows/gradle.yml)
[![C++ Documentation](https://img.shields.io/badge/documentation-c%2B%2B-blue)](https://github.wpilib.org/allwpilib/docs/development/cpp/)
[![Java Documentation](https://img.shields.io/badge/documentation-java-orange)](https://github.wpilib.org/allwpilib/docs/development/java/)
@@ -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.

View File

@@ -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()

View File

@@ -0,0 +1,7 @@
include(CMakeFindDependencyMacro)
@FILENAME_DEP_REPLACE@
@WPIMATH_DEP_REPLACE@
@WPIUTIL_DEP_REPLACE@
@FILENAME_DEP_REPLACE@
include(${SELF_DIR}/apriltag.cmake)

View File

@@ -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 {

View File

@@ -0,0 +1,89 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package 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);
}
}

View File

@@ -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));
}
}

View File

@@ -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}};
}

View File

@@ -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());

View 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"

View File

@@ -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

View File

@@ -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;

View File

@@ -409,7 +409,7 @@
}
} ],
"field" : {
"length" : 8.2296,
"width" : 16.4592
"length" : 16.4592,
"width" : 8.2296
}
}

View File

@@ -38,6 +38,7 @@ stages:
- stage: TestBench
displayName: Test Bench
condition: false
jobs:
- job: Cpp
displayName: C++

View File

@@ -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'

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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));

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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
};
/**

View File

@@ -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); }
};
/**

View File

@@ -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.
*/

View File

@@ -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;
}

View File

@@ -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>{};
}

View File

@@ -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;
}

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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

View File

@@ -43,5 +43,5 @@ void NTCommandSchedulerModel::Update() {
}
bool NTCommandSchedulerModel::Exists() {
return m_inst.IsConnected() && m_commands.Exists();
return m_commands.Exists();
}

View File

@@ -37,5 +37,5 @@ void NTCommandSelectorModel::Update() {
}
bool NTCommandSelectorModel::Exists() {
return m_inst.IsConnected() && m_running.Exists();
return m_running.Exists();
}

View File

@@ -56,5 +56,5 @@ void NTDifferentialDriveModel::Update() {
}
bool NTDifferentialDriveModel::Exists() {
return m_inst.IsConnected() && m_lPercent.Exists();
return m_lPercent.Exists();
}

View File

@@ -33,5 +33,5 @@ void NTDigitalInputModel::Update() {
}
bool NTDigitalInputModel::Exists() {
return m_inst.IsConnected() && m_value.Exists();
return m_value.Exists();
}

View File

@@ -40,5 +40,5 @@ void NTDigitalOutputModel::Update() {
}
bool NTDigitalOutputModel::Exists() {
return m_inst.IsConnected() && m_value.Exists();
return m_value.Exists();
}

View File

@@ -73,5 +73,5 @@ void NTFMSModel::Update() {
}
bool NTFMSModel::Exists() {
return m_inst.IsConnected() && m_controlWord.Exists();
return m_controlWord.Exists();
}

View File

@@ -171,7 +171,7 @@ void NTField2DModel::Update() {
}
bool NTField2DModel::Exists() {
return m_inst.IsConnected() && m_nameTopic.Exists();
return m_nameTopic.Exists();
}
bool NTField2DModel::IsReadOnly() {

View File

@@ -30,5 +30,5 @@ void NTGyroModel::Update() {
}
bool NTGyroModel::Exists() {
return m_inst.IsConnected() && m_angle.Exists();
return m_angle.Exists();
}

View File

@@ -81,5 +81,5 @@ void NTMecanumDriveModel::Update() {
}
bool NTMecanumDriveModel::Exists() {
return m_inst.IsConnected() && m_flPercent.Exists();
return m_flPercent.Exists();
}

View File

@@ -335,7 +335,7 @@ void NTMechanism2DModel::Update() {
}
bool NTMechanism2DModel::Exists() {
return m_inst.IsConnected() && m_nameTopic.Exists();
return m_nameTopic.Exists();
}
bool NTMechanism2DModel::IsReadOnly() {

View File

@@ -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();
}

View File

@@ -70,5 +70,5 @@ void NTPIDControllerModel::Update() {
}
bool NTPIDControllerModel::Exists() {
return m_inst.IsConnected() && m_setpoint.Exists();
return m_setpoint.Exists();
}

View File

@@ -60,5 +60,5 @@ void NTStringChooserModel::Update() {
}
bool NTStringChooserModel::Exists() {
return m_inst.IsConnected() && m_options.Exists();
return m_options.Exists();
}

View File

@@ -34,5 +34,5 @@ void NTSubsystemModel::Update() {
}
bool NTSubsystemModel::Exists() {
return m_inst.IsConnected() && m_defaultCommand.Exists();
return m_defaultCommand.Exists();
}

View File

@@ -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();

View File

@@ -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(

View File

@@ -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; }

View File

@@ -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;

View File

@@ -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)

View File

@@ -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 ""
)

View 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;
}
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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.
*

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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
/** @} */

View File

@@ -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.
*

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -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);

View File

@@ -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.
*

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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
View File

@@ -0,0 +1,20 @@
cppHeaderFileInclude {
\.h$
\.inc$
}
cppSrcFileInclude {
\.cpp$
}
modifiableFileExclude {
}
generatedFileExclude {
}
repoRootNameOverride {
}
includeOtherLibs {
}

View File

@@ -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)

View File

@@ -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 ""
)

View File

@@ -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"

View File

@@ -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'
}
}
}
}

View File

@@ -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.

View File

@@ -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(

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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");

View File

@@ -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);

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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