Compare commits

...

63 Commits

Author SHA1 Message Date
Tyler Veness
2f310a748c [wpimath] Fix DCMotor.getSpeed() (#5061)
This bug didn't occur in C++ because the units system caught it at
compile time.
2023-02-05 13:21:16 -08:00
Nick Hadley
b43ec87f57 [wpilib] ElevatorSim: Fix WouldHitLimit methods (#5057) 2023-02-05 11:58:53 -08:00
Peter Johnson
19267bef0c [ntcore] Output warning on property set on unpublished topic (#5059)
Previously this was a debug-level message. This can primarily impact
users who call SetPersistent() on an entry before calling SetDefault().
2023-02-05 11:57:29 -08:00
Peter Johnson
84cbd48d84 [ntcore] Handle excludeSelf on SetDefault (#5058) 2023-02-05 11:57:09 -08:00
Peter Johnson
1f35750865 [cameraserver] Add GetInstance() to all functions (#5054)
GetInstance() is required to start the event listener that creates the
network table entries.

This is a C++ only change; Java uses static's and thus doesn't need this.

The right fix is to implement cscore's AddListener() immediate notification,
but that's much too invasive of a change to do this year.

This fixes the common use cases, but doesn't fix all cases, as e.g. creating
a UsbCamera manually before calling any CameraServer functions will still
have the issue, but there's an easy workaround--call
CameraServer::SetSize() prior to creating any cameras.
2023-02-05 11:28:53 -08:00
Peter Johnson
8230fc631d [wpilib] Revert throw on nonexistent SimDevice name in SimDeviceSim (#5053)
This breaks current vendor use of SimDeviceSim.

This reverts commit d991f6e435 (#5041).
2023-02-05 11:27:55 -08:00
Peter Johnson
b879a6f8c6 [wpinet] WebSocket: When Close() is called, call closed immediately (#5047)
This provides the closed callback with the real reason for the
connection being closed.  Keep closed from being called twice by adding
a check in SetClosed().
2023-02-03 22:59:19 -08:00
Peter Johnson
49459d3e45 [ntcore] Change wire timeout to fixed 1 second (#5048)
Previously the timeout was 10 times the update rate, so with low update
rates it could be as small as 50 ms, causing spurious disconnects when
large or many topics were published.
2023-02-03 22:05:41 -08:00
Jordan McMichael
4079eabe9b [wpimath] Discard stale pose estimates (#5045)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-02-03 22:04:30 -08:00
Peter Johnson
fe5d226a19 [glass] Fix option for debug-level NT logging (#5049) 2023-02-03 22:03:45 -08:00
Peter Johnson
b7535252c2 [ntcore] Don't leak buffers in rare WS shutdown case (#5046)
If the request called the callback after the WebSocket had been
destroyed, the buffers were leaked.
2023-02-03 21:56:35 -08:00
Peter Johnson
b61ac6db33 [ntcore] Add client disconnect function (#5022)
As setServer doesn't disconnect, it's useful to have a function that
disconnects without needing to completely stop the client.
2023-02-03 15:28:00 -08:00
Ryan Blue
7b828ce84f [wpimath] Add nearest to Pose2d and Translation2d (#4882)
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2023-02-03 15:27:16 -08:00
Michael Leong
08a536291b [examples] Improvements to Elevator Simulation Example (#4937)
Co-authored-by: Abhay Shukla <105139789+aboombadev@users.noreply.github.com>
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2023-02-03 15:23:06 -08:00
Peter Johnson
193a10d020 [wpigui] Limit frame rate to 120 fps by default (#5030)
Limiting with vsync is apparently unreliable on a number of systems;
this resulted in high CPU/GPU usage.

Also add current actual frame rate to about dialog of GUI tools.
2023-02-03 15:21:52 -08:00
sciencewhiz
7867bbde0e [wpilib] Clarify DS functions provided by FMS (NFC) (#5043) 2023-02-03 15:21:12 -08:00
Peter Johnson
fa7c01b598 [glass] Add option for debug-level NT logging (#5007) 2023-02-03 15:20:03 -08:00
truher
2b81610248 [wpiutil] Add msgpack to datalog Python example (#5032) 2023-02-03 15:19:44 -08:00
Tyler Veness
a4a369b8da CONTRIBUTING.md: Add unicodeit CLI to math docs guidelines (#5031) 2023-02-03 15:19:01 -08:00
Ryan Blue
d991f6e435 [wpilib] Throw on nonexistent SimDevice name in SimDeviceSim constructor (#5041)
Previously this would just create a object that was otherwise non-functional.
2023-02-03 15:18:31 -08:00
Peter Johnson
a27a047ae8 [hal] Check for null in getSimDeviceName JNI (#5038) 2023-02-01 23:25:55 -08:00
Starlight220
2f96cae31a [examples] Hatchbots: Add telemetry (#5011) 2023-01-31 23:44:18 -08:00
Peter Johnson
83ef8f9658 [simulation] GUI: Fix buffer overflow in joystick axes copy (#5036)
This was using an incorrect sizeof which would copy excessive data and
overwrite the button data.
2023-01-31 23:40:22 -08:00
Starlight220
4054893669 [commands] Fix C++ Select() factory (#5024)
Update example to use it.
2023-01-29 07:23:12 -08:00
Sriman Achanta
f75acd11ce [commands] Use Timer.restart() (#5023) 2023-01-29 07:21:07 -08:00
Tyler Veness
8bf67b1b33 [wpimath] PIDController::Calculate(double, double): update setpoint flag (#5021)
Fixes #5020.
2023-01-29 07:18:48 -08:00
Peter Johnson
49bb1358d8 [wpiutil] MemoryBuffer: Fix GetMemoryBufferForStream (#5017)
This would previously just write past the end of the buffer, smashing
the stack.  It's only called in the case when a non-file or block device
is used as the file.
2023-01-28 22:38:34 -08:00
Peter Johnson
9c4c07c0f9 [wpiutil] Remove NDEBUG check for debug-level logging (#5018)
This adds minimal overhead but is useful when debugging release
binaries.
2023-01-28 14:13:58 -08:00
Peter Johnson
1a47cc2e86 [ntcore] Use full handle when subscribing (#5013)
Just using the index is insufficient because of Subscriber overlap with
MultiSubscriber.
2023-01-27 09:14:38 -08:00
Ryan Blue
7cd30cffbc Ignore networktables.json (#5006) 2023-01-26 09:21:58 -08:00
DeltaDizzy
92aecab2ef [commands] Command controllers are not subclasses (NFC) (#5000) 2023-01-25 15:20:29 -08:00
Peter Johnson
8785bba080 [ntcore] Special-case default timestamps (#5003)
Previously, a setDefault() on the server could override a client doing a
real set() if the time offset between client and server was negative,
resulting in a negative timestamp from the client.  This is a not
uncommon situation with robot code, as the robot code always starts at
time 0, so any clients that set values earlier (in real time) would have
negative timestamps.

Also improve special casing of 0 in the transmit side to make sure a
normal timestamp will never get sent as 0.
2023-01-25 11:36:13 -08:00
Peter Johnson
9e5b7b8040 [ntcore] Handle topicsonly followed by value subscribe (#4991)
Previously this wouldn't send the last value on the value subscribe if a
topics only subscription already existed.

Also start adding server implementation unit tests.
2023-01-24 21:50:38 -08:00
Sriman Achanta
917906530a [wpilib] Add Timer::Restart() (#4963) 2023-01-23 14:50:46 -08:00
Tyler Veness
00aa66e4fd [wpimath] Remove extraneous assignments from DiscretizeAB() (#4967) 2023-01-23 09:46:12 -08:00
Starlight220
893320544a [examples] C++ RamseteCommand: Fix units (#4954)
Also fix the memory leak in the command-based auto.
2023-01-22 11:20:35 -08:00
Evan
b95d0e060d [wpilib] XboxController: Fix docs discrepancy (NFC) (#4993)
Comments for leftTrigger said they attach to the right trigger of the controller.
2023-01-21 22:09:55 -08:00
Peter Johnson
008232b43c [ntcore] Write empty persistent file if none found (#4996)
This avoids the warning appearing on every startup when persistent
values aren't used.

Also add note to message saying it can be ignored if persistent values
aren't expected.
2023-01-21 22:09:24 -08:00
Starlight220
522be348f4 [examples] Rewrite tags (NFC) (#4961) 2023-01-21 15:24:10 -08:00
Tyler Veness
d48a83dee2 [wpimath] Update Wikipedia links for quaternion to Euler angle conversion (NFC) (#4995) 2023-01-21 15:16:35 -08:00
Peter Johnson
504fa22143 [wpimath] Workaround intellisense Eigen issue (#4992)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-01-21 15:16:12 -08:00
Brady Schindler
b2b25bf09f [commands] Fix docs inconsistency for toggleOnFalse(Command) (NFC) (#4978) 2023-01-20 23:47:37 -08:00
Thad House
ce3dc4eb3b [hal] Properly use control word that is in sync with DS data (#4989) 2023-01-20 23:46:56 -08:00
Thad House
1ea48caa7d [wpilib] Fix C++ ADXRS450 and Java SPI gyro defs (#4988) 2023-01-20 13:25:13 -08:00
Thad House
fb101925a7 [build] Include wpimathjni in commands binaries (#4981) 2023-01-19 22:05:32 -08:00
Thad House
657951f6dd [starter] Add a process starter for use by the installer for launching tools (#4931) 2023-01-19 20:09:35 -08:00
Tyler Veness
a60ca9d71c [examples] Update AprilTag field load API usage (#4975) 2023-01-19 17:01:17 -08:00
Tyler Veness
f8a45f1558 [wpimath] Remove print statements from tests (#4977) 2023-01-19 17:00:35 -08:00
sciencewhiz
ecba8b99a8 [examples] Fix swapped arguments in MecanumControllerCommand example (#4976) 2023-01-18 21:25:49 -08:00
Berke Sinan Yetkin
e95e88fdf9 [examples] Add comment to drivedistanceoffboard example (#4877)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2023-01-18 20:46:41 -08:00
CarloWoolsey
371d15dec3 [examples] Add Computer Vision Pose Estimation and Latency Compensation Example (#4901)
This PR updates the existing differentialdriveposeestimator example to include computer vision pose estimation and latency compensation.

The example generates a simulated cameraToTarget transformation, which is then fed into ComputerVisionUtil.objectToRobotPose() to compute the robot's field-relative position exclusively from vision measurements. The vision measurements are applied through DifferentialDrivePoseEstimator.addVisionMeasurement().

The updated example constructs an AprilTagFieldLayout from JSON. This requires a deploy directory, something which isn't currently supported in wpilibjExamples and wpilibcExamples.
2023-01-18 20:46:05 -08:00
Peter Johnson
cb9b8938af [sim] Enable docking in the GUI (#4960) 2023-01-18 20:42:58 -08:00
Brennen Puth
3b084ecbe0 [apriltag] AprilTagFieldLayout: Improve API shape for loading builtin JSONs (#4949) 2023-01-18 20:42:39 -08:00
Matt
27ba096ea1 [wpilib] Fix MOI calculation error in SingleJointedArmSim (#4968)
Previous calculation derivation mixed up length and distance to CG.
2023-01-18 20:40:39 -08:00
Jordan McMichael
42c997a3c4 [wpimath] Fix Pose3d exponential and clean up Pose3d logarithm (#4970)
Implementation based on this paper: https://ethaneade.org/lie.pdf
2023-01-18 20:38:03 -08:00
Tyler Veness
5f1a025f27 [wpilibj] Fix typo in MecanumDrive docs (NFC) (#4969) 2023-01-18 13:47:27 -08:00
Tyler Veness
0ebf79b54c [wpimath] Fix typo in Pose3d::Exp() docs (NFC) (#4966) 2023-01-18 13:46:45 -08:00
Oliver W
a8c465f3fb [wpimath] HolonomicDriveController: Add getters for the controllers (#4948) 2023-01-16 08:33:15 -08:00
Starlight220
a7b1ab683d [wpilibc] Add unit test for fast deconstruction of GenericHID (#4953) 2023-01-16 08:28:06 -08:00
Starlight220
bd6479dc29 [build] Add Spotless for JSON (#4956) 2023-01-16 08:26:46 -08:00
Thad House
5cb0340a8c [hal, wpilib] Load joystick values upon code initialization (#4950)
During HAL_Initialize, wait up to 100ms for a DS packet to be received. Then in RobotBase, right after calling HAL_Initialize, call each language's RefreshData function to force a high level DS update. If the DS is connected, will get joystick data. If there is no data, nothing different will happen, but in that case there's no joysticks anyway.
2023-01-15 16:36:44 -08:00
sciencewhiz
ab0e8c37a7 [readme] Update build requirements (NFC) (#4947)
Change to adoptium, and add Xcode min version
2023-01-15 15:19:24 -08:00
Tyler Veness
b74ac1c645 [build] Add apriltag to C++ cmake example builds (#4944)
This fixes compilation of the apriltag vision example on my machine.
2023-01-13 23:24:14 -08:00
216 changed files with 4498 additions and 2346 deletions

2
.gitignore vendored
View File

@@ -9,6 +9,8 @@ simgui-ds.json
simgui-window.json
simgui.json
networktables.json
# Created by the jenkins test script
test-reports

View File

@@ -40,8 +40,39 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
### Math documentation
When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `<pre>` tags in Javadoc so they render with monospace font.
The LaTeX to Unicode conversions can also be done locally via the unicodeit Python package. To install it, execute:
```bash
pip install --user unicodeit
```
Here's example usage:
```bash
$ python -m unicodeit.cli 'x_{k+1} = Ax_k + Bu_k'
xₖ₊₁ = Axₖ + Buₖ
```
On Linux, this process can be streamlined further by adding the following Bash function to your .bashrc (requires `wl-clipboard` on Wayland or `xclip` on X11):
```bash
# Converts LaTeX to Unicode, prints the result, and copies it to the clipboard
uc() {
if [ $WAYLAND_DISPLAY ]; then
python -m unicodeit.cli $@ | tee >(wl-copy -n)
else
python -m unicodeit.cli $@ | tee >(xclip -sel)
fi
}
```
Here's example usage:
```bash
$ uc 'x_{k+1} = Ax_k + Bu_k'
xₖ₊₁ = Axₖ + Buₖ
```
## Submitting Changes
### Pull Request Format

View File

@@ -40,7 +40,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
## Requirements
- [JDK 11](https://adoptopenjdk.net/)
- [JDK 11](https://adoptium.net/temurin/releases/?version=11)
- Note that the JRE is insufficient; the full JDK is required
- On Ubuntu, run `sudo apt install openjdk-11-jdk`
- On Windows, install the JDK 11 .msi from the link above
@@ -48,7 +48,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
- C++ compiler
- On Linux, install GCC 11 or greater
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
- On macOS, install the Xcode command-line build tools via `xcode-select --install`. Xcode 13 or later is required.
- ARM compiler toolchain
- Run `./gradlew installRoboRioToolchain` after cloning this repository
- If the WPILib installer was used, this toolchain is already installed

View File

@@ -189,7 +189,10 @@ public class AprilTagFieldLayout {
}
/**
* Deserializes a field layout from a resource within a jar file.
* Deserializes a field layout from a resource within a internal jar file.
*
* <p>Users should use {@link AprilTagFields#loadAprilTagLayoutField()} to load official layouts
* and {@link #AprilTagFieldLayout(String)} for custom layouts.
*
* @param resourcePath The absolute path of the resource
* @return The deserialized layout

View File

@@ -4,6 +4,8 @@
package edu.wpi.first.apriltag;
import java.io.IOException;
public enum AprilTagFields {
k2022RapidReact("2022-rapidreact.json"),
k2023ChargedUp("2023-chargedup.json");
@@ -18,4 +20,14 @@ public enum AprilTagFields {
AprilTagFields(String resourceFile) {
m_resourceFile = kBaseResourceDir + resourceFile;
}
/**
* Get a {@link AprilTagFieldLayout} from the resource JSON.
*
* @return AprilTagFieldLayout of the field
* @throws IOException If the layout does not exist
*/
public AprilTagFieldLayout loadAprilTagLayoutField() throws IOException {
return AprilTagFieldLayout.loadFromResource(m_resourceFile);
}
}

View File

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

View File

@@ -23,16 +23,13 @@ class LoadConfigTest {
@ParameterizedTest
@EnumSource(AprilTagFields.class)
void testLoad(AprilTagFields field) {
AprilTagFieldLayout layout =
Assertions.assertDoesNotThrow(
() -> AprilTagFieldLayout.loadFromResource(field.m_resourceFile));
AprilTagFieldLayout layout = Assertions.assertDoesNotThrow(field::loadAprilTagLayoutField);
assertNotNull(layout);
}
@Test
void test2022RapidReact() throws IOException {
AprilTagFieldLayout layout =
AprilTagFieldLayout.loadFromResource(AprilTagFields.k2022RapidReact.m_resourceFile);
AprilTagFieldLayout layout = AprilTagFields.k2022RapidReact.loadAprilTagLayoutField();
// Blue Hangar Truss - Hub
Pose3d expectedPose =

View File

@@ -474,6 +474,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture() {
}
cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
::GetInstance();
cs::UsbCamera camera{fmt::format("USB Camera {}", dev), dev};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -483,6 +484,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(int dev) {
cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
int dev) {
::GetInstance();
cs::UsbCamera camera{name, dev};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -492,6 +494,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
std::string_view path) {
::GetInstance();
cs::UsbCamera camera{name, path};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -517,6 +520,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
std::string_view host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -526,6 +530,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
const char* host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -535,6 +540,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
const std::string& host) {
::GetInstance();
cs::AxisCamera camera{name, host};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -544,6 +550,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
std::span<const std::string> hosts) {
::GetInstance();
cs::AxisCamera camera{name, hosts};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();
@@ -552,10 +559,11 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
}
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
auto& inst = ::GetInstance();
// create a dummy CvSource
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
cs::MjpegServer server = StartAutomaticCapture(source);
::GetInstance().m_fixedSources[server.GetHandle()] = source.GetHandle();
inst.m_fixedSources[server.GetHandle()] = source.GetHandle();
return server;
}
@@ -632,6 +640,7 @@ cs::CvSink CameraServer::GetVideo(std::string_view name) {
cs::CvSource CameraServer::PutVideo(std::string_view name, int width,
int height) {
::GetInstance();
cs::CvSource source{name, cs::VideoMode::kMJPEG, width, height, 30};
StartAutomaticCapture(source);
return source;
@@ -648,6 +657,7 @@ cs::MjpegServer CameraServer::AddServer(std::string_view name) {
}
cs::MjpegServer CameraServer::AddServer(std::string_view name, int port) {
::GetInstance();
cs::MjpegServer server{name, port};
AddServer(server);
return server;

View File

@@ -104,6 +104,8 @@ static void DisplayMainMenu() {
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}

View File

@@ -2,9 +2,18 @@
"game": "FIRST Power Up",
"field-image": "2018-field.jpg",
"field-corners": {
"top-left": [125, 20],
"bottom-right": [827, 370]
"top-left": [
125,
20
],
"bottom-right": [
827,
370
]
},
"field-size": [54, 27],
"field-size": [
54,
27
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game" : "Destination: Deep Space",
"field-image" : "2019-field.jpg",
"field-corners": {
"top-left" : [217, 40],
"bottom-right" : [1372, 615]
},
"field-size" : [54, 27],
"field-unit" : "foot"
"game": "Destination: Deep Space",
"field-image": "2019-field.jpg",
"field-corners": {
"top-left": [
217,
40
],
"bottom-right": [
1372,
615
]
},
"field-size": [
54,
27
],
"field-unit": "foot"
}

View File

@@ -1,10 +1,19 @@
{
"game" : "Infinite Recharge",
"field-image" : "2020-field.png",
"field-corners": {
"top-left" : [96, 25],
"bottom-right" : [1040, 514]
},
"field-size" : [52.4375, 26.9375],
"field-unit" : "foot"
}
"game": "Infinite Recharge",
"field-image": "2020-field.png",
"field-corners": {
"top-left": [
96,
25
],
"bottom-right": [
1040,
514
]
},
"field-size": [
52.4375,
26.9375
],
"field-unit": "foot"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Barrel Racing Path",
"field-image": "2021-barrel.png",
"field-corners": {
"top-left": [20, 20],
"bottom-right": [780, 400]
},
"field-size": [30, 15],
"field-unit": "feet"
}
"game": "Barrel Racing Path",
"field-image": "2021-barrel.png",
"field-corners": {
"top-left": [
20,
20
],
"bottom-right": [
780,
400
]
},
"field-size": [
30,
15
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Bounce Path",
"field-image": "2021-bounce.png",
"field-corners": {
"top-left": [20, 20],
"bottom-right": [780, 400]
},
"field-size": [30, 15],
"field-unit": "feet"
}
"game": "Bounce Path",
"field-image": "2021-bounce.png",
"field-corners": {
"top-left": [
20,
20
],
"bottom-right": [
780,
400
]
},
"field-size": [
30,
15
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Galactic Search A",
"field-image": "2021-galacticsearcha.png",
"field-corners": {
"top-left": [20, 20],
"bottom-right": [780, 400]
},
"field-size": [30, 15],
"field-unit": "feet"
}
"game": "Galactic Search A",
"field-image": "2021-galacticsearcha.png",
"field-corners": {
"top-left": [
20,
20
],
"bottom-right": [
780,
400
]
},
"field-size": [
30,
15
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Galactic Search B",
"field-image": "2021-galacticsearchb.png",
"field-corners": {
"top-left": [20, 20],
"bottom-right": [780, 400]
},
"field-size": [30, 15],
"field-unit": "feet"
}
"game": "Galactic Search B",
"field-image": "2021-galacticsearchb.png",
"field-corners": {
"top-left": [
20,
20
],
"bottom-right": [
780,
400
]
},
"field-size": [
30,
15
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Infinite Recharge 2021",
"field-image": "2021-field.png",
"field-corners": {
"top-left": [127, 34],
"bottom-right": [1323, 649]
},
"field-size": [52.4375, 26.9375],
"field-unit": "foot"
}
"game": "Infinite Recharge 2021",
"field-image": "2021-field.png",
"field-corners": {
"top-left": [
127,
34
],
"bottom-right": [
1323,
649
]
},
"field-size": [
52.4375,
26.9375
],
"field-unit": "foot"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Slalom Path",
"field-image": "2021-slalom.png",
"field-corners": {
"top-left": [20, 20],
"bottom-right": [780, 400]
},
"field-size": [30, 15],
"field-unit": "feet"
}
"game": "Slalom Path",
"field-image": "2021-slalom.png",
"field-corners": {
"top-left": [
20,
20
],
"bottom-right": [
780,
400
]
},
"field-size": [
30,
15
],
"field-unit": "feet"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Rapid React",
"field-image": "2022-field.png",
"field-corners": {
"top-left": [74, 50],
"bottom-right": [1774, 900]
},
"field-size": [54, 27],
"field-unit": "foot"
}
"game": "Rapid React",
"field-image": "2022-field.png",
"field-corners": {
"top-left": [
74,
50
],
"bottom-right": [
1774,
900
]
},
"field-size": [
54,
27
],
"field-unit": "foot"
}

View File

@@ -1,10 +1,19 @@
{
"game": "Charged Up",
"field-image": "2023-field.png",
"field-corners": {
"top-left": [46, 36],
"bottom-right": [1088, 544]
},
"field-size": [54.27083, 26.2916],
"field-unit": "foot"
}
"game": "Charged Up",
"field-image": "2023-field.png",
"field-corners": {
"top-left": [
46,
36
],
"bottom-right": [
1088,
544
]
},
"field-size": [
54.27083,
26.2916
],
"field-unit": "foot"
}

View File

@@ -52,6 +52,7 @@ static bool gSetEnterKey = false;
static bool gKeyEdit = false;
static int* gEnterKey;
static void (*gPrevKeyCallback)(GLFWwindow*, int, int, int, int);
static bool gNetworkTablesDebugLog = false;
static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
int action, int mods) {
@@ -72,9 +73,8 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
static void NtInitialize() {
auto inst = nt::GetDefaultInstance();
auto poller = nt::CreateListenerPoller(inst);
nt::AddPolledListener(
poller, inst,
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
nt::AddPolledLogger(poller, 0, 100);
gui::AddEarlyExecute([poller] {
auto win = gui::GetSystemWindow();
if (!win) {
@@ -98,6 +98,8 @@ static void NtInitialize() {
level = "ERROR: ";
} else if (msg->level >= NT_LOG_WARNING) {
level = "WARNING: ";
} else if (msg->level < NT_LOG_INFO && !gNetworkTablesDebugLog) {
continue;
}
gNetworkTablesLog.Append(fmt::format(
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
@@ -232,6 +234,8 @@ int main(int argc, char** argv) {
if (gNetworkTablesLogWindow) {
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
}
ImGui::MenuItem("NetworkTables Debug Logging", nullptr,
&gNetworkTablesDebugLog);
ImGui::Separator();
gNtProvider->DisplayMenu();
ImGui::EndMenu();
@@ -265,6 +269,8 @@ int main(int argc, char** argv) {
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
ImGui::Text("%.3f ms/frame (%.1f FPS)",
1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}

View File

@@ -125,7 +125,7 @@ public class DriverStationJNI extends JNIWrapper {
public static native int sendConsoleLine(String line);
public static native void refreshDSData();
public static native boolean refreshDSData();
public static native void provideNewDataEventHandle(int handle);

View File

@@ -43,6 +43,7 @@ struct JoystickDataCache {
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
HAL_AllianceStationID allianceStation;
float matchTime;
HAL_ControlWord controlWord;
};
static_assert(std::is_standard_layout_v<JoystickDataCache>);
// static_assert(std::is_trivial_v<JoystickDataCache>);
@@ -104,6 +105,8 @@ void JoystickDataCache::Update() {
FRC_NetworkCommunication_getAllianceStation(
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
FRC_NetworkCommunication_getMatchTime(&matchTime);
FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(&controlWord));
}
#define CHECK_JOYSTICK_NUMBER(stickNum) \
@@ -114,7 +117,7 @@ static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentReadLocal = &caches[0];
static std::atomic<JoystickDataCache*> currentCache{&caches[1]};
static std::atomic<JoystickDataCache*> currentCache{nullptr};
static JoystickDataCache* lastGiven = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
@@ -508,17 +511,27 @@ static void newDataOccur(uint32_t refNum) {
}
}
void HAL_RefreshDSData(void) {
HAL_Bool HAL_RefreshDSData(void) {
HAL_ControlWord controlWord;
std::memset(&controlWord, 0, sizeof(controlWord));
FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(&controlWord));
std::scoped_lock lock{cacheMutex};
JoystickDataCache* prev = currentCache.exchange(nullptr);
if (prev != nullptr) {
currentRead = prev;
JoystickDataCache* prev;
{
std::scoped_lock lock{cacheMutex};
prev = currentCache.exchange(nullptr);
if (prev != nullptr) {
currentRead = prev;
}
// If newest state shows we have a DS attached, just use the
// control word out of the cache, As it will be the one in sync
// with the data. Otherwise use the state that shows disconnected.
if (controlWord.dsAttached) {
newestControlWord = currentRead->controlWord;
} else {
newestControlWord = controlWord;
}
}
newestControlWord = controlWord;
uint32_t mask = tcpMask.exchange(0);
if (mask != 0) {
@@ -526,6 +539,7 @@ void HAL_RefreshDSData(void) {
std::scoped_lock tcpLock(tcpCacheMutex);
tcpCache.CloneTo(&tcpCurrent);
}
return prev != nullptr;
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
@@ -551,4 +565,13 @@ void InitializeDriverStation() {
setNewDataOccurRef(refNumber);
FRC_NetworkCommunication_setNewTcpDataOccurRef(tcpRefNumber);
}
void WaitForInitialPacket() {
wpi::Event waitForInitEvent;
driverStation->newDataEvents.Add(waitForInitEvent.GetHandle());
bool timed_out = false;
wpi::WaitForObject(waitForInitEvent.GetHandle(), 0.1, &timed_out);
// Don't care what the result is, just want to give it a chance.
driverStation->newDataEvents.Remove(waitForInitEvent.GetHandle());
}
} // namespace hal

View File

@@ -50,6 +50,7 @@ using namespace hal;
namespace hal {
void InitializeDriverStation();
void WaitForInitialPacket();
namespace init {
void InitializeHAL() {
InitializeCTREPCM();
@@ -546,6 +547,8 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
return rv;
});
hal::WaitForInitialPacket();
initialized = true;
return true;
}

View File

@@ -403,13 +403,13 @@ Java_edu_wpi_first_hal_DriverStationJNI_sendConsoleLine
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: refreshDSData
* Signature: ()V
* Signature: ()Z
*/
JNIEXPORT void JNICALL
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_refreshDSData
(JNIEnv*, jclass)
{
HAL_RefreshDSData();
return HAL_RefreshDSData();
}
/*

View File

@@ -468,7 +468,11 @@ JNIEXPORT jstring JNICALL
Java_edu_wpi_first_hal_simulation_SimDeviceDataJNI_getSimDeviceName
(JNIEnv* env, jclass, jint handle)
{
return MakeJString(env, HALSIM_GetSimDeviceName(handle));
const char* name = HALSIM_GetSimDeviceName(handle);
if (!name) {
return nullptr;
}
return MakeJString(env, name);
}
/*

View File

@@ -209,7 +209,7 @@ HAL_Bool HAL_GetOutputsEnabled(void);
*/
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info);
void HAL_RefreshDSData(void);
HAL_Bool HAL_RefreshDSData(void);
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle);
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle);

View File

@@ -75,7 +75,7 @@ static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentReadLocal = &caches[0];
static std::atomic<JoystickDataCache*> currentCache{&caches[1]};
static std::atomic<JoystickDataCache*> currentCache{nullptr};
static JoystickDataCache* lastGiven = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
@@ -318,9 +318,9 @@ void HAL_ObserveUserProgramTest(void) {
// TODO
}
void HAL_RefreshDSData(void) {
HAL_Bool HAL_RefreshDSData(void) {
if (gShutdown) {
return;
return false;
}
HAL_ControlWord controlWord;
std::memset(&controlWord, 0, sizeof(controlWord));
@@ -336,6 +336,7 @@ void HAL_RefreshDSData(void) {
currentRead = prev;
}
newestControlWord = controlWord;
return prev != nullptr;
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {

View File

@@ -911,6 +911,14 @@ public final class NetworkTableInstance implements AutoCloseable {
NetworkTablesJNI.setServerTeam(m_handle, team, port);
}
/**
* Disconnects the client if it's running and connected. This will automatically start
* reconnection attempts to the current server list.
*/
public void disconnect() {
NetworkTablesJNI.disconnect(m_handle);
}
/**
* Starts requesting server address from Driver Station. This connects to the Driver Station
* running on localhost to obtain the server IP address, and connects with the default port.

View File

@@ -251,6 +251,8 @@ public final class NetworkTablesJNI {
public static native void setServerTeam(int inst, int team, int port);
public static native void disconnect(int inst);
public static native void startDSClient(int inst, int port);
public static native void stopDSClient(int inst);

View File

@@ -1,366 +1,366 @@
[
{
"TypeName": "Boolean",
"TypeString": "\"boolean\"",
"c": {
"ValueType": "NT_Bool",
"ParamType": "NT_Bool"
},
"cpp": {
"ValueType": "bool",
"ParamType": "bool",
"TYPE_NAME": "BOOLEAN"
},
"java": {
"ValueType": "boolean",
"EmptyValue": "false",
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
"FunctionTypePrefix": "Boolean",
"ToWrapObject": "Boolean.valueOf",
"FromStorageBegin": "(Boolean) "
},
"jni": {
"jtype": "jboolean",
"jtypestr": "Z",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": " != JNI_FALSE",
"ToJavaBegin": "static_cast<jboolean>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJBooleanArray"
}
{
"TypeName": "Boolean",
"TypeString": "\"boolean\"",
"c": {
"ValueType": "NT_Bool",
"ParamType": "NT_Bool"
},
{
"TypeName": "Integer",
"TypeString": "\"int\"",
"c": {
"ValueType": "int64_t",
"ParamType": "int64_t"
},
"cpp": {
"ValueType": "int64_t",
"ParamType": "int64_t",
"TYPE_NAME": "INTEGER"
},
"java": {
"ValueType": "long",
"EmptyValue": "0",
"FunctionTypePrefix": "Long",
"ToWrapObject": "Long.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").longValue()"
},
"jni": {
"jtype": "jlong",
"jtypestr": "J",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jlong>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJLongArray"
}
"cpp": {
"ValueType": "bool",
"ParamType": "bool",
"TYPE_NAME": "BOOLEAN"
},
{
"TypeName": "Float",
"TypeString": "\"float\"",
"c": {
"ValueType": "float",
"ParamType": "float"
},
"cpp": {
"ValueType": "float",
"ParamType": "float",
"TYPE_NAME": "FLOAT"
},
"java": {
"ValueType": "float",
"EmptyValue": "0",
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
"SupplierFunctionPackage": "edu.wpi.first.util.function",
"FunctionTypePrefix": "Float",
"ToWrapObject": "Float.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").floatValue()"
},
"jni": {
"jtype": "jfloat",
"jtypestr": "F",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jfloat>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJFloatArray"
}
"java": {
"ValueType": "boolean",
"EmptyValue": "false",
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
"FunctionTypePrefix": "Boolean",
"ToWrapObject": "Boolean.valueOf",
"FromStorageBegin": "(Boolean) "
},
{
"TypeName": "Double",
"TypeString": "\"double\"",
"c": {
"ValueType": "double",
"ParamType": "double"
},
"cpp": {
"ValueType": "double",
"ParamType": "double",
"TYPE_NAME": "DOUBLE"
},
"java": {
"ValueType": "double",
"EmptyValue": "0",
"FunctionTypePrefix": "Double",
"ToWrapObject": "Double.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").doubleValue()"
},
"jni": {
"jtype": "jdouble",
"jtypestr": "D",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jdouble>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJDoubleArray"
}
},
{
"TypeName": "String",
"TypeString": "\"string\"",
"c": {
"ValueType": "char*",
"ParamType": "const char*",
"IsArray": true
},
"cpp": {
"ValueType": "std::string",
"ParamType": "std::string_view",
"TYPE_NAME": "STRING",
"INCLUDES": "#include <string>\n#include <string_view>\n#include <utility>",
"SmallRetType": "std::string_view",
"SmallElemType": "char"
},
"java": {
"ValueType": "String",
"EmptyValue": "\"\"",
"FunctionTypeSuffix": "<String>",
"FromStorageBegin": "(String) "
},
"jni": {
"jtype": "jstring",
"jtypestr": "Ljava/lang/String;",
"JavaObject": true,
"FromJavaBegin": "JStringRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJString(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJStringArray"
}
},
{
"TypeName": "Raw",
"c": {
"ValueType": "uint8_t*",
"ParamType": "const uint8_t*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<uint8_t>",
"ParamType": "std::span<const uint8_t>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "RAW",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<uint8_t>",
"SmallElemType": "uint8_t"
},
"java": {
"ValueType": "byte[]",
"EmptyValue": "new byte[] {}",
"FunctionTypeSuffix": "<byte[]>",
"FromStorageBegin": "(byte[]) "
},
"jni": {
"jtype": "jbyteArray",
"jtypestr": "[B",
"JavaObject": true,
"FromJavaBegin": "CriticalJByteArrayRef{env, ",
"FromJavaEnd": "}.uarray()",
"ToJavaBegin": "MakeJByteArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "BooleanArray",
"TypeString": "\"boolean[]\"",
"c": {
"ValueType": "NT_Bool*",
"ParamType": "const NT_Bool*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<int>",
"ParamType": "std::span<const int>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "BOOLEAN_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<int>",
"SmallElemType": "int"
},
"java": {
"ValueType": "boolean[]",
"WrapValueType": "Boolean[]",
"EmptyValue": "new boolean[] {}",
"FunctionTypeSuffix": "<boolean[]>",
"FromStorageBegin": "(boolean[]) "
},
"jni": {
"jtype": "jbooleanArray",
"jtypestr": "[Z",
"JavaObject": true,
"FromJavaBegin": "FromJavaBooleanArray(env, ",
"FromJavaEnd": ")",
"ToJavaBegin": "MakeJBooleanArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "IntegerArray",
"TypeString": "\"int[]\"",
"c": {
"ValueType": "int64_t*",
"ParamType": "const int64_t*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<int64_t>",
"ParamType": "std::span<const int64_t>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "INTEGER_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<int64_t>",
"SmallElemType": "int64_t"
},
"java": {
"ValueType": "long[]",
"WrapValueType": "Long[]",
"EmptyValue": "new long[] {}",
"FunctionTypeSuffix": "<long[]>",
"FromStorageBegin": "(long[]) "
},
"jni": {
"jtype": "jlongArray",
"jtypestr": "[J",
"JavaObject": true,
"FromJavaBegin": "CriticalJLongArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJLongArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "FloatArray",
"TypeString": "\"float[]\"",
"c": {
"ValueType": "float*",
"ParamType": "const float*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<float>",
"ParamType": "std::span<const float>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "FLOAT_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<float>",
"SmallElemType": "float"
},
"java": {
"ValueType": "float[]",
"WrapValueType": "Float[]",
"EmptyValue": "new float[] {}",
"FunctionTypeSuffix": "<float[]>",
"FromStorageBegin": "(float[]) "
},
"jni": {
"jtype": "jfloatArray",
"jtypestr": "[F",
"JavaObject": true,
"FromJavaBegin": "CriticalJFloatArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJFloatArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "DoubleArray",
"TypeString": "\"double[]\"",
"c": {
"ValueType": "double*",
"ParamType": "const double*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<double>",
"ParamType": "std::span<const double>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "DOUBLE_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<double>",
"SmallElemType": "double"
},
"java": {
"ValueType": "double[]",
"WrapValueType": "Double[]",
"EmptyValue": "new double[] {}",
"FunctionTypeSuffix": "<double[]>",
"FromStorageBegin": "(double[]) "
},
"jni": {
"jtype": "jdoubleArray",
"jtypestr": "[D",
"JavaObject": true,
"FromJavaBegin": "CriticalJDoubleArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJDoubleArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "StringArray",
"TypeString": "\"string[]\"",
"c": {
"ValueType": "struct NT_String*",
"ParamType": "const struct NT_String*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<std::string>",
"ParamType": "std::span<const std::string>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "STRING_ARRAY",
"INCLUDES": "#include <utility>"
},
"java": {
"ValueType": "String[]",
"EmptyValue": "new String[] {}",
"FunctionTypeSuffix": "<String[]>",
"FromStorageBegin": "(String[]) "
},
"jni": {
"jtype": "jobjectArray",
"jtypestr": "[Ljava/lang/Object;",
"JavaObject": true,
"FromJavaBegin": "FromJavaStringArray(env, ",
"FromJavaEnd": ")",
"ToJavaBegin": "MakeJStringArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
"jni": {
"jtype": "jboolean",
"jtypestr": "Z",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": " != JNI_FALSE",
"ToJavaBegin": "static_cast<jboolean>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJBooleanArray"
}
},
{
"TypeName": "Integer",
"TypeString": "\"int\"",
"c": {
"ValueType": "int64_t",
"ParamType": "int64_t"
},
"cpp": {
"ValueType": "int64_t",
"ParamType": "int64_t",
"TYPE_NAME": "INTEGER"
},
"java": {
"ValueType": "long",
"EmptyValue": "0",
"FunctionTypePrefix": "Long",
"ToWrapObject": "Long.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").longValue()"
},
"jni": {
"jtype": "jlong",
"jtypestr": "J",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jlong>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJLongArray"
}
},
{
"TypeName": "Float",
"TypeString": "\"float\"",
"c": {
"ValueType": "float",
"ParamType": "float"
},
"cpp": {
"ValueType": "float",
"ParamType": "float",
"TYPE_NAME": "FLOAT"
},
"java": {
"ValueType": "float",
"EmptyValue": "0",
"ConsumerFunctionPackage": "edu.wpi.first.util.function",
"SupplierFunctionPackage": "edu.wpi.first.util.function",
"FunctionTypePrefix": "Float",
"ToWrapObject": "Float.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").floatValue()"
},
"jni": {
"jtype": "jfloat",
"jtypestr": "F",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jfloat>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJFloatArray"
}
},
{
"TypeName": "Double",
"TypeString": "\"double\"",
"c": {
"ValueType": "double",
"ParamType": "double"
},
"cpp": {
"ValueType": "double",
"ParamType": "double",
"TYPE_NAME": "DOUBLE"
},
"java": {
"ValueType": "double",
"EmptyValue": "0",
"FunctionTypePrefix": "Double",
"ToWrapObject": "Double.valueOf",
"FromStorageBegin": "((Number) ",
"FromStorageEnd": ").doubleValue()"
},
"jni": {
"jtype": "jdouble",
"jtypestr": "D",
"JavaObject": false,
"FromJavaBegin": "",
"FromJavaEnd": "",
"ToJavaBegin": "static_cast<jdouble>(",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJDoubleArray"
}
},
{
"TypeName": "String",
"TypeString": "\"string\"",
"c": {
"ValueType": "char*",
"ParamType": "const char*",
"IsArray": true
},
"cpp": {
"ValueType": "std::string",
"ParamType": "std::string_view",
"TYPE_NAME": "STRING",
"INCLUDES": "#include <string>\n#include <string_view>\n#include <utility>",
"SmallRetType": "std::string_view",
"SmallElemType": "char"
},
"java": {
"ValueType": "String",
"EmptyValue": "\"\"",
"FunctionTypeSuffix": "<String>",
"FromStorageBegin": "(String) "
},
"jni": {
"jtype": "jstring",
"jtypestr": "Ljava/lang/String;",
"JavaObject": true,
"FromJavaBegin": "JStringRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJString(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJStringArray"
}
},
{
"TypeName": "Raw",
"c": {
"ValueType": "uint8_t*",
"ParamType": "const uint8_t*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<uint8_t>",
"ParamType": "std::span<const uint8_t>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "RAW",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<uint8_t>",
"SmallElemType": "uint8_t"
},
"java": {
"ValueType": "byte[]",
"EmptyValue": "new byte[] {}",
"FunctionTypeSuffix": "<byte[]>",
"FromStorageBegin": "(byte[]) "
},
"jni": {
"jtype": "jbyteArray",
"jtypestr": "[B",
"JavaObject": true,
"FromJavaBegin": "CriticalJByteArrayRef{env, ",
"FromJavaEnd": "}.uarray()",
"ToJavaBegin": "MakeJByteArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "BooleanArray",
"TypeString": "\"boolean[]\"",
"c": {
"ValueType": "NT_Bool*",
"ParamType": "const NT_Bool*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<int>",
"ParamType": "std::span<const int>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "BOOLEAN_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<int>",
"SmallElemType": "int"
},
"java": {
"ValueType": "boolean[]",
"WrapValueType": "Boolean[]",
"EmptyValue": "new boolean[] {}",
"FunctionTypeSuffix": "<boolean[]>",
"FromStorageBegin": "(boolean[]) "
},
"jni": {
"jtype": "jbooleanArray",
"jtypestr": "[Z",
"JavaObject": true,
"FromJavaBegin": "FromJavaBooleanArray(env, ",
"FromJavaEnd": ")",
"ToJavaBegin": "MakeJBooleanArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "IntegerArray",
"TypeString": "\"int[]\"",
"c": {
"ValueType": "int64_t*",
"ParamType": "const int64_t*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<int64_t>",
"ParamType": "std::span<const int64_t>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "INTEGER_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<int64_t>",
"SmallElemType": "int64_t"
},
"java": {
"ValueType": "long[]",
"WrapValueType": "Long[]",
"EmptyValue": "new long[] {}",
"FunctionTypeSuffix": "<long[]>",
"FromStorageBegin": "(long[]) "
},
"jni": {
"jtype": "jlongArray",
"jtypestr": "[J",
"JavaObject": true,
"FromJavaBegin": "CriticalJLongArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJLongArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "FloatArray",
"TypeString": "\"float[]\"",
"c": {
"ValueType": "float*",
"ParamType": "const float*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<float>",
"ParamType": "std::span<const float>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "FLOAT_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<float>",
"SmallElemType": "float"
},
"java": {
"ValueType": "float[]",
"WrapValueType": "Float[]",
"EmptyValue": "new float[] {}",
"FunctionTypeSuffix": "<float[]>",
"FromStorageBegin": "(float[]) "
},
"jni": {
"jtype": "jfloatArray",
"jtypestr": "[F",
"JavaObject": true,
"FromJavaBegin": "CriticalJFloatArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJFloatArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "DoubleArray",
"TypeString": "\"double[]\"",
"c": {
"ValueType": "double*",
"ParamType": "const double*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<double>",
"ParamType": "std::span<const double>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "DOUBLE_ARRAY",
"INCLUDES": "#include <utility>",
"SmallRetType": "std::span<double>",
"SmallElemType": "double"
},
"java": {
"ValueType": "double[]",
"WrapValueType": "Double[]",
"EmptyValue": "new double[] {}",
"FunctionTypeSuffix": "<double[]>",
"FromStorageBegin": "(double[]) "
},
"jni": {
"jtype": "jdoubleArray",
"jtypestr": "[D",
"JavaObject": true,
"FromJavaBegin": "CriticalJDoubleArrayRef{env, ",
"FromJavaEnd": "}",
"ToJavaBegin": "MakeJDoubleArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
},
{
"TypeName": "StringArray",
"TypeString": "\"string[]\"",
"c": {
"ValueType": "struct NT_String*",
"ParamType": "const struct NT_String*",
"IsArray": true
},
"cpp": {
"ValueType": "std::vector<std::string>",
"ParamType": "std::span<const std::string>",
"DefaultValueCopy": "defaultValue.begin(), defaultValue.end()",
"TYPE_NAME": "STRING_ARRAY",
"INCLUDES": "#include <utility>"
},
"java": {
"ValueType": "String[]",
"EmptyValue": "new String[] {}",
"FunctionTypeSuffix": "<String[]>",
"FromStorageBegin": "(String[]) "
},
"jni": {
"jtype": "jobjectArray",
"jtypestr": "[Ljava/lang/Object;",
"JavaObject": true,
"FromJavaBegin": "FromJavaStringArray(env, ",
"FromJavaEnd": ")",
"ToJavaBegin": "MakeJStringArray(env, ",
"ToJavaEnd": ")",
"ToJavaArray": "MakeJObjectArray"
}
}
]

View File

@@ -18,6 +18,7 @@ class INetworkClient {
virtual void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers) = 0;
virtual void Disconnect() = 0;
virtual void StartDSClient(unsigned int port) = 0;
virtual void StopDSClient() = 0;

View File

@@ -501,7 +501,8 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
if (topic->type != NT_UNASSIGNED && topic->type != value.type()) {
return false;
}
if (!topic->lastValue || value.time() >= topic->lastValue.time()) {
if (!topic->lastValue || topic->lastValue.time() == 0 ||
value.time() >= topic->lastValue.time()) {
// TODO: notify option even if older value
topic->type = value.type();
topic->lastValue = value;
@@ -909,6 +910,7 @@ std::unique_ptr<EntryData> LSImpl::RemoveEntry(NT_Entry entryHandle) {
MultiSubscriberData* LSImpl::AddMultiSubscriber(
std::span<const std::string_view> prefixes, const PubSubOptions& options) {
DEBUG4("AddMultiSubscriber({})", fmt::join(prefixes, ","));
auto subscriber = m_multiSubscribers.Add(m_inst, prefixes, options);
// subscribe to any already existing topics
for (auto&& topic : m_topics) {
@@ -920,6 +922,7 @@ MultiSubscriberData* LSImpl::AddMultiSubscriber(
}
}
if (m_network) {
DEBUG4("-> NetworkSubscribe");
m_network->Subscribe(subscriber->handle, subscriber->prefixes,
subscriber->options);
}
@@ -1227,6 +1230,10 @@ PublisherData* LSImpl::PublishEntry(EntryData* entry, NT_Type type) {
// create publisher
entry->publisher = AddLocalPublisher(entry->topic, wpi::json::object(),
entry->subscriber->config);
// exclude publisher if requested
if (entry->subscriber->config.excludeSelf) {
entry->subscriber->config.excludePublisher = entry->publisher->handle;
}
return entry->publisher;
}
@@ -1279,9 +1286,6 @@ 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.excludePublisher = publisher->handle;
}
}
if (!publisher) {
return false;

View File

@@ -492,6 +492,11 @@ void NetworkClient::SetServers(
m_impl->SetServers(servers, NT_DEFAULT_PORT4);
}
void NetworkClient::Disconnect() {
m_impl->m_loopRunner.ExecAsync(
[this](auto&) { m_impl->Disconnect("requested by application"); });
}
void NetworkClient::StartDSClient(unsigned int port) {
m_impl->StartDSClient(port);
}
@@ -535,6 +540,11 @@ void NetworkClient3::SetServers(
m_impl->SetServers(servers, NT_DEFAULT_PORT3);
}
void NetworkClient3::Disconnect() {
m_impl->m_loopRunner.ExecAsync(
[this](auto&) { m_impl->Disconnect("requested by application"); });
}
void NetworkClient3::StartDSClient(unsigned int port) {
m_impl->StartDSClient(port);
}

View File

@@ -38,6 +38,7 @@ class NetworkClient final : public INetworkClient {
void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers) final;
void Disconnect() final;
void StartDSClient(unsigned int port) final;
void StopDSClient() final;
@@ -59,6 +60,7 @@ class NetworkClient3 final : public INetworkClient {
void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers) final;
void Disconnect() final;
void StartDSClient(unsigned int port) final;
void StopDSClient() final;

View File

@@ -360,8 +360,16 @@ void NSImpl::LoadPersistent() {
auto size = fs::file_size(m_persistentFilename, ec);
wpi::raw_fd_istream is{m_persistentFilename, ec};
if (ec.value() != 0) {
INFO("could not open persistent file '{}': {}", m_persistentFilename,
ec.message());
INFO(
"could not open persistent file '{}': {} "
"(this can be ignored if you aren't expecting persistent values)",
m_persistentFilename, ec.message());
// try to write an empty file so it doesn't happen again
wpi::raw_fd_ostream os{m_persistentFilename, ec, fs::F_Text};
if (ec.value() == 0) {
os << "[]\n";
os.close();
}
return;
}
is.readinto(m_persistentData, size);

View File

@@ -1298,6 +1298,18 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_setServerTeam
nt::SetServerTeam(inst, team, port);
}
/*
* Class: edu_wpi_first_networktables_NetworkTablesJNI
* Method: disconnect
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_networktables_NetworkTablesJNI_disconnect
(JNIEnv* env, jclass, jint inst)
{
nt::Disconnect(inst);
}
/*
* Class: edu_wpi_first_networktables_NetworkTablesJNI
* Method: startDSClient

View File

@@ -30,9 +30,9 @@ using namespace nt::net;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
namespace {
@@ -58,7 +58,7 @@ class CImpl : public ServerMessageHandler {
bool SendControl(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs, bool flush);
void SendInitialValues();
bool CheckNetworkReady();
bool CheckNetworkReady(uint64_t curTimeMs);
// ServerMessageHandler interface
void ServerAnnounce(std::string_view name, int64_t id,
@@ -98,7 +98,6 @@ class CImpl : public ServerMessageHandler {
// periodic sweep handling
uint32_t m_periodMs{kPingIntervalMs + 10};
uint64_t m_lastSendMs{0};
int m_notReadyCount{0};
// outgoing queue
std::vector<ClientMessage> m_outgoing;
@@ -208,7 +207,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
// start a timestamp RTT ping if it's time to do one
if (curTimeMs >= m_nextPingTimeMs) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return false;
}
auto now = wpi::Now();
@@ -219,7 +218,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
}
if (!m_outgoing.empty()) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return false;
}
auto writer = m_wire.SendText();
@@ -258,7 +257,7 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
(flush || curTimeMs >= pub->nextSendMs)) {
for (auto&& val : pub->outValues) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -268,6 +267,10 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
int64_t time = val.time();
if (time != 0) {
time += m_serverTimeOffsetUs;
// make sure resultant time isn't exactly 0
if (time == 0) {
time = 1;
}
}
WireEncodeBinary(writer.Add(), Handle{pub->handle}.GetIndex(), time,
val);
@@ -308,15 +311,13 @@ void CImpl::SendInitialValues() {
}
}
bool CImpl::CheckNetworkReady() {
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}

View File

@@ -48,9 +48,9 @@ using namespace mpack;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
namespace {
@@ -214,7 +214,6 @@ class ClientData4 final : public ClientData4Base {
private:
std::vector<ServerMessage> m_outgoing;
int m_notReadyCount{0};
bool WriteBinary(int64_t id, int64_t time, const Value& value) {
return WireEncodeBinary(SendBinary().Add(), id, time, value);
@@ -293,7 +292,6 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
std::vector<net3::Message3> m_outgoing;
int64_t m_nextPubUid{1};
int m_notReadyCount{0};
struct TopicData3 {
explicit TopicData3(TopicData* topic) { UpdateFlags(topic); }
@@ -601,13 +599,17 @@ void ClientData4Base::ClientSetProperties(std::string_view name,
auto topicIt = m_server.m_nameTopics.find(name);
if (topicIt == m_server.m_nameTopics.end() ||
!topicIt->second->IsPublished()) {
DEBUG3("ignored SetProperties from {} on non-existent topic '{}'", m_id,
name);
WARNING(
"server ignoring SetProperties({}) from client {} on unpublished topic "
"'{}'; publish or set a value first",
update.dump(), m_id, name);
return; // nothing to do
}
auto topic = topicIt->second;
if (topic->special) {
DEBUG3("ignored SetProperties from {} on meta topic '{}'", m_id, name);
WARNING(
"server ignoring SetProperties({}) from client {} on meta topic '{}'",
update.dump(), m_id, name);
return; // nothing to do
}
m_server.SetProperties(nullptr, topic, update);
@@ -656,10 +658,13 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
// is client already subscribed?
bool wasSubscribed = false;
bool wasSubscribedValue = false;
for (auto subscriber : topic->subscribers) {
if (subscriber->client == this) {
wasSubscribed = true;
break;
if (!subscriber->options.topicsOnly) {
wasSubscribedValue = true;
}
}
}
@@ -673,16 +678,17 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
m_server.UpdateMetaTopicSub(topic.get());
}
if (!wasSubscribed && added && !removed) {
// announce topic to client
// announce topic to client if not previously announced
if (added && !removed && !wasSubscribed) {
DEBUG4("client {}: announce {}", m_id, topic->name);
SendAnnounce(topic.get(), std::nullopt);
}
// send last value
if (!sub->options.topicsOnly && topic->lastValue) {
DEBUG4("send last value for {} to client {}", topic->name, m_id);
SendValue(topic.get(), topic->lastValue, kSendAll);
}
// send last value
if (added && !sub->options.topicsOnly && !wasSubscribedValue &&
topic->lastValue) {
DEBUG4("send last value for {} to client {}", topic->name, m_id);
SendValue(topic.get(), topic->lastValue, kSendAll);
}
}
@@ -937,13 +943,11 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
}
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
m_wire.Disconnect("transmit stalled");
}
return;
}
m_notReadyCount = 0;
for (auto&& msg : m_outgoing) {
if (auto m = std::get_if<ServerValueMsg>(&msg.contents)) {
@@ -1110,13 +1114,11 @@ void ClientData3::SendOutgoing(uint64_t curTimeMs) {
}
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
m_wire.Disconnect("transmit stalled");
}
return;
}
m_notReadyCount = 0;
auto out = m_wire.Send();
for (auto&& msg : m_outgoing) {
@@ -2125,7 +2127,7 @@ void SImpl::SetFlags(ClientData* client, TopicData* topic, unsigned int flags) {
void SImpl::SetValue(ClientData* client, TopicData* topic, const Value& value) {
// update retained value if from same client or timestamp newer
if (!topic->lastValue || topic->lastValueClient == client ||
value.time() >= topic->lastValue.time()) {
topic->lastValue.time() == 0 || value.time() >= topic->lastValue.time()) {
DEBUG4("updating '{}' last value (time was {} is {})", topic->name,
topic->lastValue.time(), value.time());
topic->lastValue = value;

View File

@@ -50,6 +50,10 @@ void WebSocketConnection::Flush() {
if (self->m_sendsActive > 0) {
--self->m_sendsActive;
}
} else {
for (auto&& buf : bufs) {
buf.Deallocate();
}
}
});
m_frames.clear();

View File

@@ -142,10 +142,9 @@ bool nt::net::WireEncodeText(wpi::raw_ostream& os, const ClientMessage& msg) {
} else if (auto m = std::get_if<SetPropertiesMsg>(&msg.contents)) {
WireEncodeSetProperties(os, m->name, m->update);
} else if (auto m = std::get_if<SubscribeMsg>(&msg.contents)) {
WireEncodeSubscribe(os, Handle{m->subHandle}.GetIndex(), m->topicNames,
m->options);
WireEncodeSubscribe(os, m->subHandle, m->topicNames, m->options);
} else if (auto m = std::get_if<UnsubscribeMsg>(&msg.contents)) {
WireEncodeUnsubscribe(os, Handle{m->subHandle}.GetIndex());
WireEncodeUnsubscribe(os, m->subHandle);
} else {
return false;
}

View File

@@ -31,9 +31,9 @@ using namespace nt::net3;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
namespace {
@@ -93,7 +93,7 @@ class CImpl : public MessageHandler3 {
void HandleLocal(std::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs, bool initial, bool flush);
void SendValue(Writer& out, Entry* entry, const Value& value);
bool CheckNetworkReady();
bool CheckNetworkReady(uint64_t curTimeMs);
// Outgoing handlers
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
@@ -142,7 +142,6 @@ class CImpl : public MessageHandler3 {
uint32_t m_periodMs{kKeepAliveIntervalMs + 10};
uint64_t m_lastSendMs{0};
uint64_t m_nextKeepAliveTimeMs;
int m_notReadyCount{0};
// indexed by publisher index
std::vector<std::unique_ptr<PublisherData>> m_publishers;
@@ -235,7 +234,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
// send keep-alives
if (curTimeMs >= m_nextKeepAliveTimeMs) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
DEBUG4("Sending keep alive");
@@ -246,7 +245,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
// send any stored-up flags updates
if (!m_outgoingFlags.empty()) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
for (auto&& p : m_outgoingFlags) {
@@ -261,7 +260,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
if (pub && !pub->outValues.empty() &&
(flush || curTimeMs >= pub->nextSendMs)) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -302,15 +301,13 @@ void CImpl::SendValue(Writer& out, Entry* entry, const Value& value) {
}
}
bool CImpl::CheckNetworkReady() {
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}

View File

@@ -537,6 +537,10 @@ void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
nt::SetServerTeam(inst, team, port);
}
void NT_Disconnect(NT_Inst inst) {
nt::Disconnect(inst);
}
void NT_StartDSClient(NT_Inst inst, unsigned int port) {
nt::StartDSClient(inst, port);
}

View File

@@ -685,6 +685,14 @@ void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
}
}
void Disconnect(NT_Inst inst) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
if (auto client = ii->GetClient()) {
client->Disconnect();
}
}
}
void StartDSClient(NT_Inst inst, unsigned int port) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
if (auto client = ii->GetClient()) {

View File

@@ -586,6 +586,12 @@ class NetworkTableInstance final {
*/
void SetServerTeam(unsigned int team, unsigned int port = 0);
/**
* Disconnects the client if it's running and connected. This will
* automatically start reconnection attempts to the current server list.
*/
void Disconnect();
/**
* Starts requesting server address from Driver Station.
* This connects to the Driver Station running on localhost to obtain the

View File

@@ -163,6 +163,10 @@ inline void NetworkTableInstance::SetServerTeam(unsigned int team,
::nt::SetServerTeam(m_handle, team, port);
}
inline void NetworkTableInstance::Disconnect() {
::nt::Disconnect(m_handle);
}
inline void NetworkTableInstance::StartDSClient(unsigned int port) {
::nt::StartDSClient(m_handle, port);
}

View File

@@ -1147,6 +1147,14 @@ void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names,
*/
void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
/**
* Disconnects the client if it's running and connected. This will automatically
* start reconnection attempts to the current server list.
*
* @param inst instance handle
*/
void NT_Disconnect(NT_Inst inst);
/**
* Starts requesting server address from Driver Station.
* This connects to the Driver Station running on localhost to obtain the

View File

@@ -1087,6 +1087,14 @@ void SetServer(
*/
void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
/**
* Disconnects the client if it's running and connected. This will automatically
* start reconnection attempts to the current server list.
*
* @param inst instance handle
*/
void Disconnect(NT_Inst inst);
/**
* Starts requesting server address from Driver Station.
* This connects to the Driver Station running on localhost to obtain the

View File

@@ -33,8 +33,10 @@ class SpanMatcher : public ::testing::MatcherInterface<std::span<T>> {
};
template <typename T>
inline ::testing::Matcher<std::span<const T>> SpanEq(std::span<const T> good) {
return ::testing::MakeMatcher(new SpanMatcher(good));
inline ::testing::Matcher<std::span<const typename T::value_type>> SpanEq(
const T& good) {
return ::testing::MakeMatcher(
new SpanMatcher(std::span<const typename T::value_type>(good)));
}
template <typename T>

View File

@@ -0,0 +1,333 @@
// 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 <stdint.h>
#include <span>
#include <string_view>
#include <vector>
#include "../MockLogger.h"
#include "../PubSubOptionsMatcher.h"
#include "../SpanMatcher.h"
#include "../TestPrinters.h"
#include "../ValueMatcher.h"
#include "Handle.h"
#include "MockNetworkInterface.h"
#include "MockWireConnection.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "net/Message.h"
#include "net/ServerImpl.h"
#include "net/WireEncoder.h"
#include "ntcore_c.h"
#include "ntcore_cpp.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Property;
using ::testing::Return;
using MockSetPeriodicFunc = ::testing::MockFunction<void(uint32_t repeatMs)>;
using MockConnected3Func =
::testing::MockFunction<void(std::string_view name, uint16_t proto)>;
namespace nt {
class ServerImplTest : public ::testing::Test {
public:
::testing::StrictMock<net::MockLocalInterface> local;
wpi::MockLogger logger;
net::ServerImpl server{logger};
};
TEST_F(ServerImplTest, AddClient) {
::testing::StrictMock<net::MockWireConnection> wire;
EXPECT_CALL(wire, Flush());
MockSetPeriodicFunc setPeriodic;
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
EXPECT_EQ(name, "test");
EXPECT_NE(id, -1);
}
TEST_F(ServerImplTest, AddDuplicateClient) {
::testing::StrictMock<net::MockWireConnection> wire1;
::testing::StrictMock<net::MockWireConnection> wire2;
MockSetPeriodicFunc setPeriodic1;
MockSetPeriodicFunc setPeriodic2;
EXPECT_CALL(wire1, Flush());
EXPECT_CALL(wire2, Flush());
auto [name1, id1] = server.AddClient("test", "connInfo", false, wire1,
setPeriodic1.AsStdFunction());
auto [name2, id2] = server.AddClient("test", "connInfo2", false, wire2,
setPeriodic2.AsStdFunction());
EXPECT_EQ(name1, "test");
EXPECT_NE(id1, -1);
EXPECT_EQ(name2, "test@1");
EXPECT_NE(id1, id2);
EXPECT_NE(id2, -1);
}
TEST_F(ServerImplTest, AddClient3) {}
template <typename T>
static std::string EncodeText(const T& msgs) {
std::string data;
wpi::raw_string_ostream os{data};
bool first = true;
for (auto&& msg : msgs) {
if (first) {
os << '[';
first = false;
} else {
os << ',';
}
net::WireEncodeText(os, msg);
}
os << ']';
return data;
}
template <typename T>
static std::vector<uint8_t> EncodeServerBinary(const T& msgs) {
std::vector<uint8_t> data;
wpi::raw_uvector_ostream os{data};
for (auto&& msg : msgs) {
if constexpr (std::is_same_v<typename T::value_type, net::ServerMessage>) {
if (auto m = std::get_if<net::ServerValueMsg>(&msg.contents)) {
net::WireEncodeBinary(os, m->topic, m->value.time(), m->value);
}
} else if constexpr (std::is_same_v<typename T::value_type,
net::ClientMessage>) {
if (auto m = std::get_if<net::ClientValueMsg>(&msg.contents)) {
net::WireEncodeBinary(os, Handle{m->pubHandle}.GetIndex(),
m->value.time(), m->value);
}
}
}
return data;
}
TEST_F(ServerImplTest, PublishLocal) {
// publish before client connect
server.SetLocal(&local);
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
NT_Publisher pubHandle2 = nt::Handle{0, 2, nt::Handle::kPublisher};
NT_Topic topicHandle2 = nt::Handle{0, 2, nt::Handle::kTopic};
NT_Publisher pubHandle3 = nt::Handle{0, 3, nt::Handle::kPublisher};
NT_Topic topicHandle3 = nt::Handle{0, 3, nt::Handle::kTopic};
{
::testing::InSequence seq;
EXPECT_CALL(local, NetworkAnnounce("test", "double", wpi::json::object(),
pubHandle));
EXPECT_CALL(local, NetworkAnnounce("test2", "double", wpi::json::object(),
pubHandle2));
EXPECT_CALL(local, NetworkAnnounce("test3", "double", wpi::json::object(),
pubHandle3));
}
{
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
server.HandleLocal(msgs);
}
// client connect; it should get already-published topic as soon as it
// subscribes
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
EXPECT_CALL(wire, Flush()); // AddClient()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
{
std::vector<net::ServerMessage> smsgs;
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::json::object()}});
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
"test2", 8, "double", std::nullopt, wpi::json::object()}});
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendControl()
}
EXPECT_CALL(wire, Flush()); // SendControl()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
{
std::vector<net::ServerMessage> smsgs;
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
"test3", 11, "double", std::nullopt, wpi::json::object()}});
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendControl()
}
EXPECT_CALL(wire, Flush()); // SendControl()
}
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
{
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::SubscribeMsg{
subHandle, {{""}}, PubSubOptions{.prefixMatch = true}}});
server.ProcessIncomingText(id, EncodeText(msgs));
}
// publish before send control
{
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle2, topicHandle2, "test2", "double", wpi::json::object(), {}}});
server.HandleLocal(msgs);
}
server.SendControl(100);
// publish after send control
{
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle3, topicHandle3, "test3", "double", wpi::json::object(), {}}});
server.HandleLocal(msgs);
}
server.SendControl(200);
}
TEST_F(ServerImplTest, ClientSubTopicOnlyThenValue) {
// publish before client connect
server.SetLocal(&local);
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
EXPECT_CALL(
local, NetworkAnnounce("test", "double", wpi::json::object(), pubHandle));
{
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
msgs.emplace_back(net::ClientMessage{
net::ClientValueMsg{pubHandle, Value::MakeDouble(1.0, 10)}});
server.HandleLocal(msgs);
}
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
EXPECT_CALL(wire, Flush()); // AddClient()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
{
std::vector<net::ServerMessage> smsgs;
smsgs.emplace_back(net::ServerMessage{net::AnnounceMsg{
"test", 3, "double", std::nullopt, wpi::json::object()}});
EXPECT_CALL(wire, Text(EncodeText(smsgs))); // SendValues()
}
EXPECT_CALL(wire, Flush()); // SendValues()
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
EXPECT_CALL(wire, Flush()); // ClientSubscribe()
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
{
std::vector<net::ServerMessage> smsgs;
smsgs.emplace_back(net::ServerMessage{
net::ServerValueMsg{3, Value::MakeDouble(1.0, 10)}});
EXPECT_CALL(
wire,
Binary(wpi::SpanEq(EncodeServerBinary(smsgs)))); // SendValues()
}
EXPECT_CALL(wire, Flush()); // SendValues()
}
// connect client
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
// subscribe topics only; will not send value
{
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::SubscribeMsg{
subHandle,
{{""}},
PubSubOptions{.topicsOnly = true, .prefixMatch = true}}});
server.ProcessIncomingText(id, EncodeText(msgs));
}
server.SendValues(id, 100);
// subscribe normal; will not resend announcement, but will send value
{
NT_Subscriber subHandle = nt::Handle{0, 2, nt::Handle::kSubscriber};
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{
net::SubscribeMsg{subHandle, {{"test"}}, PubSubOptions{}}});
server.ProcessIncomingText(id, EncodeText(msgs));
}
server.SendValues(id, 200);
}
TEST_F(ServerImplTest, ZeroTimestampNegativeTime) {
// publish before client connect
server.SetLocal(&local);
NT_Publisher pubHandle = nt::Handle{0, 1, nt::Handle::kPublisher};
NT_Topic topicHandle = nt::Handle{0, 1, nt::Handle::kTopic};
NT_Subscriber subHandle = nt::Handle{0, 1, nt::Handle::kSubscriber};
Value defaultValue = Value::MakeDouble(1.0, 10);
defaultValue.SetTime(0);
defaultValue.SetServerTime(0);
Value value = Value::MakeDouble(5, -10);
{
::testing::InSequence seq;
EXPECT_CALL(local, NetworkAnnounce("test", "double", wpi::json::object(),
pubHandle))
.WillOnce(Return(topicHandle));
EXPECT_CALL(local, NetworkSetValue(topicHandle, defaultValue));
EXPECT_CALL(local, NetworkSetValue(topicHandle, value));
}
{
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle, topicHandle, "test", "double", wpi::json::object(), {}}});
msgs.emplace_back(
net::ClientMessage{net::ClientValueMsg{pubHandle, defaultValue}});
msgs.emplace_back(
net::ClientMessage{net::SubscribeMsg{subHandle, {"test"}, {}}});
server.HandleLocal(msgs);
}
// client connect; it should get already-published topic as soon as it
// subscribes
::testing::StrictMock<net::MockWireConnection> wire;
MockSetPeriodicFunc setPeriodic;
{
::testing::InSequence seq;
EXPECT_CALL(wire, Flush()); // AddClient()
}
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
setPeriodic.AsStdFunction());
// publish and send non-default value with negative time offset
{
NT_Subscriber pubHandle2 = nt::Handle{0, 2, nt::Handle::kPublisher};
std::vector<net::ClientMessage> msgs;
msgs.emplace_back(net::ClientMessage{net::PublishMsg{
pubHandle2, topicHandle, "test", "double", wpi::json::object(), {}}});
server.ProcessIncomingText(id, EncodeText(msgs));
msgs.clear();
msgs.emplace_back(
net::ClientMessage{net::ClientValueMsg{pubHandle2, value}});
server.ProcessIncomingBinary(id, EncodeServerBinary(msgs));
}
}
} // namespace nt

View File

@@ -172,14 +172,15 @@ TEST_F(WireEncoderTextTest, MessageSubscribe) {
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(),
"{\"method\":\"subscribe\",\"params\":{"
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":5}}");
"\"options\":{},\"topics\":[\"a\",\"b\"],\"subuid\":402653189}}");
}
TEST_F(WireEncoderTextTest, MessageUnsubscribe) {
net::ClientMessage msg{
net::UnsubscribeMsg{Handle{0, 5, Handle::kSubscriber}}};
ASSERT_TRUE(net::WireEncodeText(os, msg));
ASSERT_EQ(os.str(), "{\"method\":\"unsubscribe\",\"params\":{\"subuid\":5}}");
ASSERT_EQ(os.str(),
"{\"method\":\"unsubscribe\",\"params\":{\"subuid\":402653189}}");
}
TEST_F(WireEncoderTextTest, MessageAnnounce) {

View File

@@ -183,6 +183,8 @@ static void DisplayGui() {
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}

View File

@@ -0,0 +1,84 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena')) {
description = "Process Starter"
apply plugin: 'cpp'
apply plugin: 'objective-cpp'
apply plugin: 'visual-studio'
apply plugin: 'edu.wpi.first.NativeUtils'
ext {
nativeName = 'processstarter'
}
apply from: "${rootDir}/shared/config.gradle"
// Replace shared crt with static crt.
// Note this means no wpilib binaries can be dependencies
nativeUtils.platformConfigs.named(nativeUtils.wpi.platforms.windowsx64).configure {
cppCompiler.debugArgs.remove('/MDd')
cppCompiler.debugArgs.add('/MTd')
cppCompiler.releaseArgs.remove('/MD')
cppCompiler.releaseArgs.add('/MT')
}
project(':').libraryBuild.dependsOn build
model {
components {
"${nativeName}"(NativeExecutableSpec) {
baseName = 'processstarter'
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.sources {
macObjCpp(ObjectiveCppSourceSet) {
source {
srcDirs 'src/main/native/osx'
include '**/*.mm'
}
exportedHeaders {
srcDirs 'src/main/native/include'
include '**/*.h'
}
}
}
} else if (it.targetPlatform.operatingSystem.isLinux()) {
it.sources {
linuxCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/linux'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include'
include '**/*.h'
}
}
}
} else if (it.targetPlatform.operatingSystem.isWindows()) {
it.sources {
windowsCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/windows'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include'
include '**/*.h'
}
}
}
}
}
}
}
}
apply from: 'publish.gradle'
}

View File

@@ -0,0 +1,67 @@
apply plugin: 'maven-publish'
def baseArtifactId = 'processstarter'
def artifactGroupId = 'edu.wpi.first.tools'
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_processstarter_CLS'
def outputsFolder = file("$project.buildDir/outputs")
model {
tasks {
// Create the run task.
$.components.processstarter.binaries.each { bin ->
if (bin.buildable && bin.name.toLowerCase().contains("debug") && nativeUtils.isNativeDesktopPlatform(bin.targetPlatform)) {
Task run = project.tasks.create("run", Exec) {
commandLine bin.tasks.install.runScriptFile.get().asFile.toString()
}
run.dependsOn bin.tasks.install
}
}
}
publishing {
def processstarterTaskList = []
$.components.each { component ->
component.binaries.each { binary ->
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("processstarter")) {
if (binary.buildable && (binary.name.contains('Release') || binary.name.contains('release'))) {
// We are now in the binary that we want.
// This is the default application path for the ZIP task.
def applicationPath = binary.executable.file
// Create the ZIP.
def task = project.tasks.create("copyprocessstarterExecutable" + binary.targetPlatform.architecture.name, Zip) {
description("Copies the processstarter executable to the outputs directory.")
destinationDirectory = outputsFolder
archiveBaseName = '_M_' + zipBaseName
duplicatesStrategy = 'exclude'
classifier = nativeUtils.getPublishClassifier(binary)
from(licenseFile) {
into '/'
}
from(applicationPath)
}
task.dependsOn binary.tasks.link
processstarterTaskList.add(task)
project.build.dependsOn task
project.artifacts { task }
addTaskToCopyAllOutputs(task)
}
}
}
}
publications {
processstarter(MavenPublication) {
processstarterTaskList.each { artifact it }
artifactId = baseArtifactId
groupId = artifactGroupId
version wpilibVersioning.version.get()
}
}
}
}

View File

@@ -0,0 +1,86 @@
// 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 <poll.h>
#include <spawn.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <climits>
#include <cstdio>
#include <cstring>
#include <filesystem>
int main(int argc, char* argv[]) {
char path[PATH_MAX];
char dest[PATH_MAX];
std::memset(dest, 0, sizeof(dest)); // readlink does not null terminate!
pid_t pid = getpid();
std::snprintf(path, PATH_MAX, "/proc/%d/exe", pid);
int readlink_len = readlink(path, dest, PATH_MAX);
if (readlink_len < 0) {
std::perror("readlink");
return 1;
} else if (readlink_len == PATH_MAX) {
std::printf("Truncation occured\n");
return 1;
}
std::filesystem::path exePath{dest};
if (exePath.empty()) {
return 1;
}
if (!exePath.has_stem()) {
return 1;
}
if (!exePath.has_parent_path()) {
return 1;
}
std::filesystem::path jarPath{exePath};
jarPath.replace_extension("jar");
std::filesystem::path parentPath{exePath.parent_path()};
if (!parentPath.has_parent_path()) {
return 1;
}
std::filesystem::path toolsFolder{parentPath.parent_path()};
std::filesystem::path Java = toolsFolder / "jdk" / "bin" / "java";
pid = 0;
std::string data = jarPath;
std::string jarArg = "-jar";
char* const arguments[] = {jarArg.data(), data.data(), nullptr};
int status =
posix_spawn(&pid, Java.c_str(), nullptr, nullptr, arguments, environ);
if (status != 0) {
char* home = std::getenv("JAVA_HOME");
std::string javaLocal = "java";
if (home != nullptr) {
std::filesystem::path javaHomePath{home};
javaHomePath /= "bin";
javaHomePath /= "java";
javaLocal = javaHomePath;
}
status = posix_spawn(&pid, javaLocal.c_str(), nullptr, nullptr, arguments,
environ);
if (status != 0) {
return 1;
}
}
int childPid = syscall(SYS_pidfd_open, pid, 0);
if (childPid <= 0) {
return 1;
}
struct pollfd pfd = {childPid, POLLIN, 0};
return poll(&pfd, 1, 3000);
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#import <Foundation/Foundation.h>
#include <string>
#include <filesystem>
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
NSString* exePathPlat = [[NSBundle mainBundle] bundlePath];
NSString* identifier = [[NSBundle mainBundle] bundleIdentifier];
if (identifier == nil) {
exePathPlat = [[NSBundle mainBundle] executablePath];
}
std::filesystem::path exePath{[exePathPlat UTF8String]};
if (exePath.empty()) {
return 1;
}
if (!exePath.has_stem()) {
return 1;
}
if (!exePath.has_parent_path()) {
return 1;
}
std::filesystem::path jarPath{exePath};
jarPath.replace_extension("jar");
std::filesystem::path parentPath{exePath.parent_path()};
if (!parentPath.has_parent_path()) {
return 1;
}
std::filesystem::path toolsFolder{parentPath.parent_path()};
std::filesystem::path java = toolsFolder / "jdk" / "bin" / "java";
NSArray<NSString*>* Arguments =
@[ @"-jar", [NSString stringWithFormat:@"%s", jarPath.c_str()] ];
NSTask* task = [[NSTask alloc] init];
task.launchPath = [NSString stringWithFormat:@"%s", java.c_str()];
task.arguments = Arguments;
task.terminationHandler = ^(NSTask* t) {
(void)t;
CFRunLoopStop(CFRunLoopGetMain());
};
if (![task launchAndReturnError:nil]) {
task.terminationHandler = nil;
NSString* javaHome =
[[[NSProcessInfo processInfo] environment] objectForKey:@"JAVA_HOME"];
task = [[NSTask alloc] init];
task.launchPath = @"java";
if (javaHome != nil) {
std::filesystem::path javaHomePath{[javaHome UTF8String]};
javaHomePath /= "bin";
javaHomePath /= "java";
task.launchPath = [NSString stringWithFormat:@"%s", javaHomePath.c_str()];
}
task.arguments = Arguments;
task.terminationHandler = ^(NSTask* t) {
(void)t;
CFRunLoopStop(CFRunLoopGetMain());
};
if (![task launchAndReturnError:nil]) {
return 1;
}
}
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3, false);
return task.running ? 0 : 1;
}

View File

@@ -0,0 +1,85 @@
// 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 <filesystem>
#include "Windows.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR pCmdLine, int nCmdShow) {
DWORD Status;
WCHAR ExePathRaw[1024];
Status = GetModuleFileNameW(NULL, ExePathRaw, 1024);
if (Status == 0) {
DWORD LastError = GetLastError();
return LastError;
}
std::filesystem::path ExePath{ExePathRaw};
if (ExePath.empty()) {
return 1;
}
if (!ExePath.has_stem()) {
return 1;
}
if (!ExePath.has_parent_path()) {
return 1;
}
std::filesystem::path JarPath{ExePath};
JarPath.replace_extension(L"jar");
std::filesystem::path ParentPath{ExePath.parent_path()};
if (!ParentPath.has_parent_path()) {
return 1;
}
std::filesystem::path ToolsFolder{ParentPath.parent_path()};
std::filesystem::path Javaw = ToolsFolder / L"jdk" / L"bin" / L"javaw.exe";
std::wstring ToRun = L" -jar \"";
ToRun += JarPath;
ToRun += L"\"";
STARTUPINFOW StartupInfo;
PROCESS_INFORMATION ProcessInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
if (!CreateProcessW(Javaw.c_str(), ToRun.data(), NULL, NULL, FALSE, 0, NULL,
NULL, &StartupInfo, &ProcessInfo)) {
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
ToRun = L" -jar \"";
ToRun += JarPath;
ToRun += L"\"";
Status = GetEnvironmentVariableW(L"JAVA_HOME", ExePathRaw, 1024);
std::wstring JavawLocal = L"javaw";
if (Status != 0 && Status < 1024) {
std::filesystem::path JavaHomePath{ExePathRaw};
JavaHomePath /= "bin";
JavaHomePath /= "javaw.exe";
JavawLocal = JavaHomePath;
}
if (!CreateProcessW(JavawLocal.c_str(), ToRun.data(), NULL, NULL, FALSE, 0,
NULL, NULL, &StartupInfo, &ProcessInfo)) {
return 1;
}
}
Status =
WaitForSingleObject(ProcessInfo.hProcess, 3000); // Wait for 3 seconds
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
return Status == WAIT_TIMEOUT ? 0 : 1;
}

View File

@@ -127,6 +127,8 @@ static void DisplayGui() {
static_cast<int>(multicastResolver->HasImplementation()));
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}

View File

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

View File

@@ -42,6 +42,46 @@ task checkTemplates(type: Task) {
}
}
def tagList = [
/* --- Categories --- */
// On-RIO image processing
"Vision",
// Command-based
"Command-based",
// Romi
"Romi",
// Extremely simple programs showcasing a single hardware API
"Hardware",
// Full robot, with multiple mechanisms
"Complete Robot",
// A single mechanism in the Robot class
"Basic Robot",
/* --- Mechanisms --- */
"Intake", "Flywheel", "Elevator", "Arm", "Differential Drive", "Mecanum Drive",
"Swerve Drive",
/* --- Telemetry --- */
"SmartDashboard", "Shuffleboard", "Sendable", "DataLog",
/* --- Controls --- */
"PID", "State-Space", "Ramsete", "Path Following", "Trajectory", "SysId",
"Simulation", "Trapezoid Profile", "Profiled PID", "Odometry", "LQR",
"Pose Estimator",
/* --- Hardware --- */
"Analog", "Ultrasonic", "Gyro", "Pneumatics", "I2C", "Duty Cycle", "PDP", "DMA", "Relay",
"AddressableLEDs", "HAL", "Encoder", "Smart Motor Controller", "Digital Input",
"Digital Output",
/* --- HID --- */
"XboxController", "PS4Controller", "Joystick",
/* --- Misc --- */
/* (try to keep this section minimal) */
"EventLoop", "AprilTags", "Mechanism2d", "Preferences",
]
task checkExamples(type: Task) {
doLast {
def parsedJson = new groovy.json.JsonSlurper().parseText(exampleFile.text)
@@ -50,6 +90,7 @@ task checkExamples(type: Task) {
assert it.name != null
assert it.description != null
assert it.tags != null
assert it.tags.findAll { !tagList.contains(it) }.empty
assert it.foldername != null
assert it.gradlebase != null
assert it.commandversion != null

View File

@@ -41,6 +41,14 @@ if (!project.hasProperty('skipJavaFormat')) {
trimTrailingWhitespace()
endWithNewline()
}
json {
target fileTree('.') {
include '**/*.json'
exclude '**/build/**', '**/build-*/**'
}
gson()
.indentWithSpaces(2)
}
format 'xml', {
target fileTree('.') {
include '**/*.xml'

View File

@@ -487,7 +487,7 @@ void GlfwSystemJoystick::GetData(HALJoystickData* data, bool mapGamepad) const {
}
} else {
std::memcpy(data->axes.axes, sysAxes,
data->axes.count * sizeof(&data->axes.axes[0]));
data->axes.count * sizeof(data->axes.axes[0]));
}
data->povs.count = data->desc.povCount;

View File

@@ -116,7 +116,8 @@ __declspec(dllexport)
}
});
if (!gui::Initialize("Robot Simulation", 1280, 720)) {
if (!gui::Initialize("Robot Simulation", 1280, 720,
ImGuiConfigFlags_DockingEnable)) {
return 0;
}
HAL_RegisterExtensionListener(

View File

@@ -0,0 +1,30 @@
From 94882f4460897f92dfe9f95ec33629094f8e76a2 Mon Sep 17 00:00:00 2001
From: Peter Johnson <johnson.peter@gmail.com>
Date: Fri, 20 Jan 2023 23:41:56 -0800
Subject: [PATCH 2/2] Intellisense fix
---
Eigen/src/Core/util/Macros.h | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Eigen/src/Core/util/Macros.h b/Eigen/src/Core/util/Macros.h
index 986c3d4..81986b9 100644
--- a/Eigen/src/Core/util/Macros.h
+++ b/Eigen/src/Core/util/Macros.h
@@ -58,6 +58,16 @@
// Compiler identification, EIGEN_COMP_*
//------------------------------------------------------------------------------------------
+/// \internal Disable NEON features in Intellisense
+#if __INTELLISENSE__
+#ifdef __ARM_NEON
+#undef __ARM_NEON
+#endif
+#ifdef __ARM_NEON__
+#undef __ARM_NEON__
+#endif
+#endif
+
/// \internal EIGEN_COMP_GNUC set to 1 for all compilers compatible with GCC
#ifdef __GNUC__
#define EIGEN_COMP_GNUC (__GNUC__*10+__GNUC_MINOR__)

View File

@@ -105,7 +105,7 @@ def main():
# Apply patches to upstream Git repo
os.chdir(upstream_root)
for f in ["0001-Disable-warnings.patch"]:
for f in ["0001-Disable-warnings.patch", "0002-Intellisense-fix.patch"]:
git_am(os.path.join(wpilib_root, "upstream_utils/eigen_patches", f))
# Delete old install

View File

@@ -4,9 +4,13 @@
#include "wpigui.h"
#include <stdint.h>
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <thread>
#include <GLFW/glfw3.h>
#include <IconsFontAwesome6.h>
@@ -88,6 +92,8 @@ static void IniReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
impl->userScale = num;
} else if (std::strncmp(lineStr, "style=", 6) == 0) {
impl->style = num;
} else if (std::strncmp(lineStr, "fps=", 4) == 0) {
impl->fps = num;
}
}
@@ -98,9 +104,10 @@ static void IniWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
}
out_buf->appendf(
"[MainWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
"xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\n\n",
"xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\nfps=%d\n\n",
gContext->width, gContext->height, gContext->maximized ? 1 : 0,
gContext->xPos, gContext->yPos, gContext->userScale, gContext->style);
gContext->xPos, gContext->yPos, gContext->userScale, gContext->style,
gContext->fps);
}
void gui::CreateContext() {
@@ -331,6 +338,8 @@ bool gui::Initialize(const char* title, int width, int height,
void gui::Main() {
// Main loop
while (!glfwWindowShouldClose(gContext->window) && !gContext->exit) {
double startTime = glfwGetTime();
// Poll and handle events (inputs, window resize, etc.)
glfwPollEvents();
gContext->isPlatformRendering = true;
@@ -351,6 +360,15 @@ void gui::Main() {
io.WantSaveIniSettings = false; // reset flag
}
}
// if FPS limiting, sleep until next frame time
if (gContext->fps != 0) {
double sleepTime = (1.0 / gContext->fps) - (glfwGetTime() - startTime);
if (sleepTime > 1e-6) {
std::this_thread::sleep_for(
std::chrono::microseconds(static_cast<int64_t>(sleepTime * 1e6)));
}
}
}
// Save (if custom save)
@@ -477,6 +495,10 @@ void gui::SetStyle(Style style) {
}
}
void gui::SetFPS(int fps) {
gContext->fps = fps;
}
void gui::SetClearColor(ImVec4 color) {
gContext->clearColor = color;
}
@@ -539,6 +561,27 @@ void gui::EmitViewMenu() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Frame Rate")) {
bool selected;
selected = gContext->fps == 0;
if (ImGui::MenuItem("vsync", nullptr, &selected)) {
gContext->fps = 0;
}
selected = gContext->fps == 30;
if (ImGui::MenuItem("30 fps", nullptr, &selected)) {
gContext->fps = 30;
}
selected = gContext->fps == 60;
if (ImGui::MenuItem("60 fps", nullptr, &selected)) {
gContext->fps = 60;
}
selected = gContext->fps == 120;
if (ImGui::MenuItem("120 fps", nullptr, &selected)) {
gContext->fps = 120;
}
ImGui::EndMenu();
}
if (!gContext->saveSettings) {
ImGui::MenuItem("Reset UI on Exit?", nullptr, &gContext->resetOnExit);
}

View File

@@ -162,6 +162,13 @@ enum Style { kStyleClassic = 0, kStyleDark, kStyleLight };
*/
void SetStyle(Style style);
/**
* Sets the FPS limit. Using this function makes this setting persistent.
*
* @param fps FPS (0=vsync)
*/
void SetFPS(int fps);
/**
* Sets the clear (background) color.
*

View File

@@ -24,6 +24,7 @@ struct SavedSettings {
int yPos = -1;
int userScale = 2;
int style = 0;
int fps = 120;
};
constexpr int kFontScaledLevels = 9;

View File

@@ -1,37 +1,37 @@
{
"fileName": "WPILibNewCommands.json",
"name": "WPILib-New-Commands",
"version": "1.0.0",
"uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",
"mavenUrls": [],
"jsonUrl": "",
"javaDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-java",
"version": "wpilib"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-cpp",
"version": "wpilib",
"libName": "wpilibNewCommands",
"headerClassifier": "headers",
"sourcesClassifier": "sources",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena",
"linuxarm32",
"linuxarm64",
"windowsx86-64",
"windowsx86",
"linuxx86-64",
"osxuniversal"
]
}
]
}
{
"fileName": "WPILibNewCommands.json",
"name": "WPILib-New-Commands",
"version": "1.0.0",
"uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266",
"mavenUrls": [],
"jsonUrl": "",
"javaDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-java",
"version": "wpilib"
}
],
"jniDependencies": [],
"cppDependencies": [
{
"groupId": "edu.wpi.first.wpilibNewCommands",
"artifactId": "wpilibNewCommands-cpp",
"version": "wpilib",
"libName": "wpilibNewCommands",
"headerClassifier": "headers",
"sourcesClassifier": "sources",
"sharedLibrary": true,
"skipInvalidPlatforms": true,
"binaryPlatforms": [
"linuxathena",
"linuxarm32",
"linuxarm64",
"windowsx86-64",
"windowsx86",
"linuxx86-64",
"osxuniversal"
]
}
]
}

View File

@@ -67,6 +67,7 @@ model {
project(':ntcore').addNtcoreJniDependency(it)
lib project: ':wpinet', library: 'wpinetJNIShared', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutilJNIShared', linkage: 'shared'
lib project: ':wpimath', library: 'wpimathJNIShared', linkage: 'shared'
project(':hal').addHalJniDependency(it)
}

View File

@@ -332,8 +332,7 @@ public class MecanumControllerCommand extends CommandBase {
m_prevSpeeds =
m_kinematics.toWheelSpeeds(new ChassisSpeeds(initialXVelocity, initialYVelocity, 0.0));
m_timer.reset();
m_timer.start();
m_timer.restart();
}
@Override

View File

@@ -143,8 +143,7 @@ public class RamseteCommand extends CommandBase {
initialState.velocityMetersPerSecond,
0,
initialState.curvatureRadPerMeter * initialState.velocityMetersPerSecond));
m_timer.reset();
m_timer.start();
m_timer.restart();
if (m_usePID) {
m_leftController.reset();
m_rightController.reset();

View File

@@ -210,8 +210,7 @@ public class SwerveControllerCommand extends CommandBase {
@Override
public void initialize() {
m_timer.reset();
m_timer.start();
m_timer.restart();
}
@Override

View File

@@ -39,8 +39,7 @@ public class TrapezoidProfileCommand extends CommandBase {
@Override
public void initialize() {
m_timer.reset();
m_timer.start();
m_timer.restart();
}
@Override

View File

@@ -30,8 +30,7 @@ public class WaitCommand extends CommandBase {
@Override
public void initialize() {
m_timer.reset();
m_timer.start();
m_timer.restart();
}
@Override

View File

@@ -9,7 +9,7 @@ import edu.wpi.first.wpilibj.event.EventLoop;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
/**
* A subclass of {@link Joystick} with {@link Trigger} factories for command-based.
* A version of {@link Joystick} with {@link Trigger} factories for command-based.
*
* @see Joystick
*/

View File

@@ -9,7 +9,7 @@ import edu.wpi.first.wpilibj.event.EventLoop;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
/**
* A subclass of {@link XboxController} with {@link Trigger} factories for command-based.
* A version of {@link XboxController} with {@link Trigger} factories for command-based.
*
* @see XboxController
*/

View File

@@ -205,7 +205,7 @@ public class Trigger implements BooleanSupplier {
}
/**
* Toggles a command when the condition changes from `true` to the low state.
* Toggles a command when the condition changes from `true` to `false`.
*
* @param command the command to toggle
* @return this trigger, so calls can be chained

View File

@@ -257,8 +257,7 @@ void MecanumControllerCommand::Initialize() {
m_prevSpeeds = m_kinematics.ToWheelSpeeds(
frc::ChassisSpeeds{initialXVelocity, initialYVelocity, 0_rad_per_s});
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
if (m_usePID) {
m_frontLeftController->Reset();
m_rearLeftController->Reset();

View File

@@ -92,8 +92,7 @@ void RamseteCommand::Initialize() {
m_prevSpeeds = m_kinematics.ToWheelSpeeds(
frc::ChassisSpeeds{initialState.velocity, 0_mps,
initialState.velocity * initialState.curvature});
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
if (m_usePID) {
m_leftController->Reset();
m_rightController->Reset();

View File

@@ -15,8 +15,7 @@ WaitCommand::WaitCommand(units::second_t duration) : m_duration{duration} {
}
void WaitCommand::Initialize() {
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
}
void WaitCommand::End(bool interrupted) {

View File

@@ -6,8 +6,10 @@
#include <functional>
#include <initializer_list>
#include <memory>
#include <span>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@@ -156,13 +158,15 @@ namespace cmd {
* @param selector the selector function
* @param commands map of commands to select from
*/
template <typename Key>
[[nodiscard]] CommandPtr Select(
std::function<Key()> selector,
std::vector<std::pair<Key, CommandPtr>> commands) {
return SelectCommand(std::move(selector),
CommandPtr::UnwrapVector(std::move(commands)))
.ToPtr();
template <typename Key, class... Types>
[[nodiscard]] CommandPtr Select(std::function<Key()> selector,
std::pair<Key, Types>&&... commands) {
std::vector<std::pair<Key, std::unique_ptr<Command>>> vec;
((void)vec.emplace_back(commands.first, std::move(commands.second).Unwrap()),
...);
return SelectCommand(std::move(selector), std::move(vec)).ToPtr();
}
// Command Groups

View File

@@ -150,8 +150,7 @@ void SwerveControllerCommand<NumModules>::Initialize() {
return m_trajectory.States().back().pose.Rotation();
};
}
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
}
template <size_t NumModules>

View File

@@ -63,10 +63,7 @@ class TrapezoidProfileCommand
this->AddRequirements(requirements);
}
void Initialize() override {
m_timer.Reset();
m_timer.Start();
}
void Initialize() override { m_timer.Restart(); }
void Execute() override { m_output(m_profile.Calculate(m_timer.Get())); }

View File

@@ -10,7 +10,7 @@
namespace frc2 {
/**
* A subclass of {@link Joystick} with {@link Trigger} factories for
* A version of {@link Joystick} with {@link Trigger} factories for
* command-based.
*
* @see Joystick

View File

@@ -10,7 +10,7 @@
namespace frc2 {
/**
* A subclass of {@link PS4Controller} with {@link Trigger} factories for
* A version of {@link PS4Controller} with {@link Trigger} factories for
* command-based.
*
* @see PS4Controller

View File

@@ -10,7 +10,7 @@
namespace frc2 {
/**
* A subclass of {@link XboxController} with {@link Trigger} factories for
* A version of {@link XboxController} with {@link Trigger} factories for
* command-based.
*
* @see XboxController

View File

@@ -194,8 +194,7 @@ class Trigger {
Trigger ToggleOnFalse(Command* command);
/**
* Toggles a command when the condition changes from `true` to the low
* state.
* Toggles a command when the condition changes from `true` to `false`.
*
* <p>Takes a raw pointer, and so is non-owning; users are responsible for the
* lifespan of the command.

View File

@@ -118,8 +118,7 @@ class MecanumControllerCommandTest {
this::setWheelSpeeds,
subsystem);
m_timer.reset();
m_timer.start();
m_timer.restart();
command.initialize();
while (!command.isFinished()) {

View File

@@ -114,8 +114,7 @@ class SwerveControllerCommandTest {
this::setModuleStates,
subsystem);
m_timer.reset();
m_timer.start();
m_timer.restart();
command.initialize();
while (!command.isFinished()) {

View File

@@ -112,8 +112,7 @@ TEST_F(MecanumControllerCommandTest, ReachesReference) {
},
{&subsystem});
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
command.Initialize();
while (!command.IsFinished()) {

View File

@@ -88,8 +88,7 @@ TEST_F(SwerveControllerCommandTest, ReachesReference) {
m_rotController,
[&](auto moduleStates) { m_moduleStates = moduleStates; }, {&subsystem});
m_timer.Reset();
m_timer.Start();
m_timer.Restart();
command.Initialize();
while (!command.IsFinished()) {

View File

@@ -69,7 +69,7 @@ bool ADXRS450_Gyro::IsConnected() const {
return m_connected;
}
static bool CalcParity(int v) {
static bool CalcParity(uint32_t v) {
bool parity = false;
while (v != 0) {
parity = !parity;
@@ -87,7 +87,7 @@ static inline int BytesToIntBE(uint8_t* buf) {
}
uint16_t ADXRS450_Gyro::ReadRegister(int reg) {
int cmd = 0x80000000 | static_cast<int>(reg) << 17;
uint32_t cmd = 0x80000000 | static_cast<int>(reg) << 17;
if (!CalcParity(cmd)) {
cmd |= 1u;
}

View File

@@ -54,6 +54,14 @@ void Timer::Start() {
}
}
void Timer::Restart() {
if (m_running) {
Stop();
}
Reset();
Start();
}
void Timer::Stop() {
if (m_running) {
m_accumulatedTime = Get();

View File

@@ -41,11 +41,11 @@ ElevatorSim::ElevatorSim(const DCMotor& gearbox, double gearing,
m_simulateGravity(simulateGravity) {}
bool ElevatorSim::WouldHitLowerLimit(units::meter_t elevatorHeight) const {
return elevatorHeight < m_minHeight;
return elevatorHeight <= m_minHeight;
}
bool ElevatorSim::WouldHitUpperLimit(units::meter_t elevatorHeight) const {
return elevatorHeight > m_maxHeight;
return elevatorHeight >= m_maxHeight;
}
bool ElevatorSim::HasHitLowerLimit() const {

View File

@@ -18,13 +18,12 @@ using namespace frc::sim;
SingleJointedArmSim::SingleJointedArmSim(
const LinearSystem<2, 1, 1>& system, const DCMotor& gearbox, double gearing,
units::meter_t armLength, units::radian_t minAngle,
units::radian_t maxAngle, units::kilogram_t armMass, bool simulateGravity,
units::radian_t maxAngle, bool simulateGravity,
const std::array<double, 1>& measurementStdDevs)
: LinearSystemSim<2, 1, 1>(system, measurementStdDevs),
m_r(armLength),
m_armLen(armLength),
m_minAngle(minAngle),
m_maxAngle(maxAngle),
m_armMass(armMass),
m_gearbox(gearbox),
m_gearing(gearing),
m_simulateGravity(simulateGravity) {}
@@ -32,12 +31,12 @@ SingleJointedArmSim::SingleJointedArmSim(
SingleJointedArmSim::SingleJointedArmSim(
const DCMotor& gearbox, double gearing, units::kilogram_square_meter_t moi,
units::meter_t armLength, units::radian_t minAngle,
units::radian_t maxAngle, units::kilogram_t armMass, bool simulateGravity,
units::radian_t maxAngle, bool simulateGravity,
const std::array<double, 1>& measurementStdDevs)
: SingleJointedArmSim(
LinearSystemId::SingleJointedArmSystem(gearbox, moi, gearing),
gearbox, gearing, armLength, minAngle, maxAngle, armMass,
simulateGravity, measurementStdDevs) {}
gearbox, gearing, armLength, minAngle, maxAngle, simulateGravity,
measurementStdDevs) {}
bool SingleJointedArmSim::WouldHitLowerLimit(units::radian_t armAngle) const {
return armAngle <= m_minAngle;
@@ -78,24 +77,37 @@ void SingleJointedArmSim::SetInputVoltage(units::volt_t voltage) {
Vectord<2> SingleJointedArmSim::UpdateX(const Vectord<2>& currentXhat,
const Vectord<1>& u,
units::second_t dt) {
// Horizontal case:
// Torque = F * r = I * alpha
// alpha = F * r / I
// Since F = mg,
// alpha = m * g * r / I
// Finally, multiply RHS by cos(theta) to account for the arm angle
// This acceleration is added to the linear system dynamics x-dot = Ax + Bu
// We therefore find that f(x, u) = Ax + Bu + [[0] [m * g * r / I *
// std::cos(theta)]]
// The torque on the arm is given by τ = F⋅r, where F is the force applied by
// gravity and r the distance from pivot to center of mass. Recall from
// dynamics that the sum of torques for a rigid body is τ = J⋅α, were τ is
// torque on the arm, J is the mass-moment of inertia about the pivot axis,
// and α is the angular acceleration in rad/s². Rearranging yields: α = F⋅r/J
//
// We substitute in F = m⋅g⋅cos(θ), where θ is the angle from horizontal:
//
// α = (m⋅g⋅cos(θ))⋅r/J
//
// Multiply RHS by cos(θ) to account for the arm angle. Further, we know the
// arm mass-moment of inertia J of our arm is given by J=1/3 mL², modeled as a
// rod rotating about it's end, where L is the overall rod length. The mass
// distribution is assumed to be uniform. Substitute r=L/2 to find:
//
// α = (m⋅g⋅cos(θ))⋅r/(1/3 mL²)
// α = (m⋅g⋅cos(θ))⋅(L/2)/(1/3 mL²)
// α = 3/2⋅g⋅cos(θ)/L
//
// This acceleration is next added to the linear system dynamics ẋ=Ax+Bu
//
// f(x, u) = Ax + Bu + [0 α]ᵀ
// f(x, u) = Ax + Bu + [0 3/2⋅g⋅cos(θ)/L]ᵀ
Vectord<2> updatedXhat = RKDP(
[&](const auto& x, const auto& u) -> Vectord<2> {
Vectord<2> xdot = m_plant.A() * x + m_plant.B() * u;
if (m_simulateGravity) {
xdot += Vectord<2>{0.0, (m_armMass * m_r * -9.8 * 3.0 /
(m_armMass * m_r * m_r) * std::cos(x(0)))
.value()};
xdot += Vectord<2>{
0.0, (3.0 / 2.0 * -9.8 / m_armLen * std::cos(x(0))).value()};
}
return xdot;
},

View File

@@ -41,6 +41,7 @@ int frc::RunHALInitialization() {
std::puts("FATAL ERROR: HAL could not be initialized");
return -1;
}
DriverStation::RefreshData();
HAL_Report(HALUsageReporting::kResourceType_Language,
HALUsageReporting::kLanguage_CPlusPlus, 0, GetWPILibVersion());

View File

@@ -227,6 +227,9 @@ class DriverStation final {
/**
* Returns the game specific message provided by the FMS.
*
* If the FMS is not connected, it is set from the game data setting on the
* driver station.
*
* @return A string containing the game specific message.
*/
static std::string GetGameSpecificMessage();
@@ -262,7 +265,10 @@ class DriverStation final {
static int GetReplayNumber();
/**
* Return the alliance that the driver station says it is on.
* Return the alliance that the driver station says it is on from the FMS.
*
* If the FMS is not connected, it is set from the team alliance setting on
* the driver station.
*
* This could return kRed or kBlue.
*
@@ -271,7 +277,10 @@ class DriverStation final {
static Alliance GetAlliance();
/**
* Return the driver station location on the field.
* Return the driver station location from the FMS.
*
* If the FMS is not connected, it is set from the team alliance setting on
* the driver station.
*
* This could return 1, 2, or 3.
*

View File

@@ -76,6 +76,14 @@ class Timer {
*/
void Start();
/**
* Restart the timer by stopping the timer, if it is not already stopped,
* resetting the accumulated time, then starting the timer again. If you
* want an event to periodically reoccur at some time interval from the
* start time, consider using AdvanceIfElapsed() instead.
*/
void Restart();
/**
* Stop the timer.
*

View File

@@ -359,7 +359,7 @@ class XboxController : public GenericHID {
* Constructs an event instance around the axis value of the left trigger.
* The returned trigger will be true when the axis value is greater than 0.5.
* @param loop the event loop instance to attach the event to.
* @return an event instance that is true when the right trigger's axis
* @return an event instance that is true when the left trigger's axis
* exceeds 0.5, attached to the given event loop
*/
BooleanEvent LeftTrigger(EventLoop* loop) const;

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