Compare commits

...

83 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
Doug Wegscheid
cf1a411acf [examples] Add example programs for AprilTags detection (#4932)
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2023-01-13 23:08:45 -08:00
sciencewhiz
1e05b21ab5 [wpimath] Fix PID atSetpoint to not return true prematurely (#4906)
Wait until setpoint and measurement have been set.
2023-01-13 22:26:30 -08:00
ohowe
e5a6197633 [wpimath] Fix SwerveDriveKinematics not initializing a new array each time (#4942)
This is problematic if you call it twice before utilizing the result.
2023-01-13 20:16:50 -08:00
Peter Johnson
039edcc23f [ntcore] Queue current value on subscriber creation (#4938)
This fixes a potential race condition in code that only uses readQueue.
2023-01-13 20:07:24 -08:00
Matt
f7f19207e0 [wpimath] Allow multiple vision measurements from same timestamp (#4917)
Co-authored-by: Jordan McMichael <jlmcmchl@gmail.com>
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-01-11 23:04:30 -08:00
Starlight220
befd12911c [commands] Delete UB-causing rvalue variants of CommandPtr methods (#4923)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2023-01-11 22:53:04 -08:00
superpenguin612
34519de60a [commands] Fix spacing in command composition exception (#4924) 2023-01-11 11:46:33 -08:00
Ryan Blue
dc4355c031 [hal] Add handle constructor and name getters for sim devices (#4925) 2023-01-11 11:45:15 -08:00
Ryan Blue
53d8d33bca [hal, wpilibj] Add missing distance per pulse functions to EncoderSim (#4928)
Also fix C++ and Java EncoderSim.setDistancePerPulse() not propagating value to SimEncoderData.
2023-01-11 11:43:56 -08:00
bovlb
530ae40614 [apriltag] Explain what April tag poses represent (NFC) (#4930) 2023-01-11 11:42:30 -08:00
Starlight220
79f565191e [examples] DigitalCommunication, I2CCommunication: Add tests (#4865) 2023-01-08 16:33:53 -08:00
Starlight220
2cd9be413f [wpilib, examples] Cleanup PotentiometerPID, Ultrasonic, UltrasonicPID examples (#4893)
Fix C++ Ultrasonic to return correct units.
2023-01-08 16:33:07 -08:00
Matt
babb0c1fcf [apriltag] Add 2023 field layout JSON (#4912) 2023-01-08 16:30:45 -08:00
ohowe
330ba45f9c [wpimath] Fix swerve kinematics util classes equals function (#4907)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-01-08 16:29:35 -08:00
sciencewhiz
51272ef6b3 [fieldImages] Add 2023 field (#4915) 2023-01-08 16:28:25 -08:00
Starlight220
0d105ab771 [commands] Deduplicate command test utils (#4897) 2023-01-08 07:44:53 -08:00
Tyler Veness
cf4235ea36 [wpiutil] Guard MSVC pragma in SymbolExports.h (#4911)
MinGW gives an unknown pragma warning on Windows.
2023-01-07 16:41:40 -08:00
Ryan Blue
2d4b7b9147 [build] Update opencv version in opencv.gradle (#4909) 2023-01-06 18:09:58 -08:00
Peter Johnson
aec6f3d506 [ntcore] Fix client flush behavior (#4903)
We need to ignore per-publisher send periods when flushing.

Also fix NT4 client to use flush async's (same as NT3 client).
2023-01-04 23:36:26 -08:00
Peter Johnson
bfe346c76a [build] Fix cmake java resources (#4898)
These need to be relative paths, but GLOB generates absolute paths by
default.
2023-01-04 08:16:47 -08:00
304 changed files with 7710 additions and 2910 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

@@ -40,7 +40,7 @@ if (WITH_JAVA)
set(CMAKE_JAVA_INCLUDE_PATH apriltag.jar ${EJML_JARS} ${JACKSON_JARS})
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
file(GLOB_RECURSE JAVA_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/main/native/resources/*.json)
add_jar(apriltag_jar
SOURCES ${JAVA_SOURCES}
RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES}

View File

@@ -37,6 +37,9 @@ import java.util.Optional;
* at the bottom-right corner of the blue alliance wall. {@link #setOrigin(OriginPosition)} can be
* used to change the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be from the
* perspective of a specific alliance.
*
* <p>Tag poses represent the center of the tag, with a zero rotation representing a tag that is
* upright and facing away from the (blue) alliance wall (that is, towards the opposing alliance).
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
@@ -186,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,17 +4,30 @@
package edu.wpi.first.apriltag;
import java.io.IOException;
public enum AprilTagFields {
k2022RapidReact("2022-rapidreact.json");
k2022RapidReact("2022-rapidreact.json"),
k2023ChargedUp("2023-chargedup.json");
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
/** Alias to the current game. */
public static final AprilTagFields kDefaultField = k2022RapidReact;
public static final AprilTagFields kDefaultField = k2023ChargedUp;
public final String m_resourceFile;
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

@@ -10,6 +10,7 @@ namespace frc {
// C++ generated from resource files
std::string_view GetResource_2022_rapidreact_json();
std::string_view GetResource_2023_chargedup_json();
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
std::string_view fieldString;
@@ -17,6 +18,9 @@ AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
case AprilTagField::k2022RapidReact:
fieldString = GetResource_2022_rapidreact_json();
break;
case AprilTagField::k2023ChargedUp:
fieldString = GetResource_2023_chargedup_json();
break;
case AprilTagField::kNumFields:
throw std::invalid_argument("Invalid Field");
}

View File

@@ -34,7 +34,11 @@ namespace frc {
* Pose3ds in the JSON are measured using the normal FRC coordinate system, NWU
* with the origin at the bottom-right corner of the blue alliance wall.
* SetOrigin(OriginPosition) can be used to change the poses returned from
* GetTagPose(int) to be from the perspective of a specific alliance. */
* GetTagPose(int) to be from the perspective of a specific alliance.
*
* Tag poses represent the center of the tag, with a zero rotation representing
* a tag that is upright and facing away from the (blue) alliance wall (that is,
* towards the opposing alliance). */
class WPILIB_DLLEXPORT AprilTagFieldLayout {
public:
enum class OriginPosition {

View File

@@ -14,6 +14,7 @@ namespace frc {
enum class AprilTagField {
k2022RapidReact,
k2023ChargedUp,
// This is a placeholder for denoting the last supported field. This should
// always be the last entry in the enum and should not be used by users

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

@@ -0,0 +1,152 @@
{
"tags": [
{
"ID": 1,
"pose": {
"translation": {
"x": 15.513558,
"y": 1.071626,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 0.0,
"X": 0.0,
"Y": 0.0,
"Z": 1.0
}
}
}
},
{
"ID": 2,
"pose": {
"translation": {
"x": 15.513558,
"y": 2.748026,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 0.0,
"X": 0.0,
"Y": 0.0,
"Z": 1.0
}
}
}
},
{
"ID": 3,
"pose": {
"translation": {
"x": 15.513558,
"y": 4.424426,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 0.0,
"X": 0.0,
"Y": 0.0,
"Z": 1.0
}
}
}
},
{
"ID": 4,
"pose": {
"translation": {
"x": 16.178784,
"y": 6.749796,
"z": 0.695452
},
"rotation": {
"quaternion": {
"W": 0.0,
"X": 0.0,
"Y": 0.0,
"Z": 1.0
}
}
}
},
{
"ID": 5,
"pose": {
"translation": {
"x": 0.36195,
"y": 6.749796,
"z": 0.695452
},
"rotation": {
"quaternion": {
"W": 1.0,
"X": 0.0,
"Y": 0.0,
"Z": 0.0
}
}
}
},
{
"ID": 6,
"pose": {
"translation": {
"x": 1.02743,
"y": 4.424426,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 1.0,
"X": 0.0,
"Y": 0.0,
"Z": 0.0
}
}
}
},
{
"ID": 7,
"pose": {
"translation": {
"x": 1.02743,
"y": 2.748026,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 1.0,
"X": 0.0,
"Y": 0.0,
"Z": 0.0
}
}
}
},
{
"ID": 8,
"pose": {
"translation": {
"x": 1.02743,
"y": 1.071626,
"z": 0.462788
},
"rotation": {
"quaternion": {
"W": 1.0,
"X": 0.0,
"Y": 0.0,
"Z": 0.0
}
}
}
}
],
"field": {
"length": 16.54175,
"width": 8.0137
}
}

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

@@ -11,7 +11,7 @@ if (WITH_JAVA)
set(CMAKE_JAVA_INCLUDE_PATH fieldImages.jar ${JACKSON_JARS})
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json src/main/native/resources/*.png src/main/native/resources/*.jpg)
file(GLOB_RECURSE JAVA_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/main/native/resources/*.json src/main/native/resources/*.png src/main/native/resources/*.jpg)
add_jar(field_images_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/fields" ${JAVA_RESOURCES} OUTPUT_NAME fieldImages)
get_property(FIELD_IMAGES_JAR_FILE TARGET field_images_jar PROPERTY JAR_FILE)

View File

@@ -14,12 +14,13 @@ public enum Fields {
k2021GalacticSearchA("2021-galacticsearcha.json"),
k2021GalacticSearchB("2021-galacticsearchb.json"),
k2021Slalom("2021-slalompath.json"),
k2022RapidReact("2022-rapidreact.json");
k2022RapidReact("2022-rapidreact.json"),
k2023ChargedUp("2023-chargedup.json");
public static final String kBaseResourceDir = "/edu/wpi/first/fields/";
/** Alias to the current game. */
public static final Fields kDefaultField = k2022RapidReact;
public static final Fields kDefaultField = k2023ChargedUp;
public final String m_resourceFile;

View File

@@ -0,0 +1,12 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
namespace fields {
std::string_view GetResource_2023_chargedup_json();
std::string_view GetResource_2023_field_png();
} // namespace fields

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

@@ -0,0 +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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

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

@@ -106,6 +106,15 @@ public class SimDevice implements AutoCloseable {
return m_handle;
}
/**
* Get the name of the simulated device.
*
* @return the name
*/
public String getName() {
return SimDeviceJNI.getSimDeviceName(m_handle);
}
/**
* Creates a value on the simulated device.
*

View File

@@ -33,6 +33,14 @@ public class SimDeviceJNI extends JNIWrapper {
*/
public static native void freeSimDevice(int handle);
/**
* Get the name of a simulated device.
*
* @param handle simulated device handle
* @return name of the simulated device
*/
public static native String getSimDeviceName(int handle);
private static native int createSimValueNative(
int device, String name, int direction, int type, long value1, double value2);

View File

@@ -79,6 +79,15 @@ public class EncoderDataJNI extends JNIWrapper {
public static native void setSamplesToAverage(int index, int samplesToAverage);
public static native int registerDistancePerPulseCallback(
int index, NotifyCallback callback, boolean initialNotify);
public static native void cancelDistancePerPulseCallback(int index, int uid);
public static native double getDistancePerPulse(int index);
public static native void setDistancePerPulse(int index, double distancePerPulse);
public static native void setDistance(int index, double distance);
public static native double getDistance(int index);

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

@@ -12,6 +12,10 @@ HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name) {
void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) {}
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle) {
return "";
}
HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device,
const char* name, int32_t direction,
const struct HAL_Value* initialValue) {

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

@@ -63,6 +63,18 @@ Java_edu_wpi_first_hal_SimDeviceJNI_freeSimDevice
HAL_FreeSimDevice(handle);
}
/*
* Class: edu_wpi_first_hal_SimDeviceJNI
* Method: getSimDeviceName
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_first_hal_SimDeviceJNI_getSimDeviceName
(JNIEnv* env, jclass, jint handle)
{
return MakeJString(env, HAL_GetSimDeviceName(handle));
}
/*
* Class: edu_wpi_first_hal_SimDeviceJNI
* Method: createSimValueNative

View File

@@ -412,6 +412,56 @@ Java_edu_wpi_first_hal_simulation_EncoderDataJNI_setSamplesToAverage
HALSIM_SetEncoderSamplesToAverage(index, value);
}
/*
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
* Method: registerDistancePerPulseCallback
* Signature: (ILjava/lang/Object;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_registerDistancePerPulseCallback
(JNIEnv* env, jclass, jint index, jobject callback, jboolean initialNotify)
{
return sim::AllocateCallback(env, index, callback, initialNotify,
&HALSIM_RegisterEncoderDistancePerPulseCallback);
}
/*
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
* Method: cancelDistancePerPulseCallback
* Signature: (II)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_cancelDistancePerPulseCallback
(JNIEnv* env, jclass, jint index, jint handle)
{
return sim::FreeCallback(env, handle, index,
&HALSIM_CancelEncoderDistancePerPulseCallback);
}
/*
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
* Method: getDistancePerPulse
* Signature: (I)D
*/
JNIEXPORT jdouble JNICALL
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_getDistancePerPulse
(JNIEnv*, jclass, jint index)
{
return HALSIM_GetEncoderDistancePerPulse(index);
}
/*
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
* Method: setDistancePerPulse
* Signature: (ID)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_setDistancePerPulse
(JNIEnv*, jclass, jint index, jdouble value)
{
HALSIM_SetEncoderDistancePerPulse(index, value);
}
/*
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
* Method: setDistance

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

@@ -9,6 +9,7 @@
#ifdef __cplusplus
#include <initializer_list>
#include <span>
#include <string>
#endif
#include "hal/Types.h"
@@ -66,6 +67,14 @@ HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name);
*/
void HAL_FreeSimDevice(HAL_SimDeviceHandle handle);
/**
* Get the name of a simulated device
*
* @param handle simulated device handle
* @return name of the simulated device
*/
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle);
/**
* Creates a value on a simulated device.
*
@@ -731,6 +740,15 @@ class SimDevice {
*/
operator HAL_SimDeviceHandle() const { return m_handle; } // NOLINT
/**
* Get the name of the simulated device.
*
* @return name
*/
std::string GetName() const {
return std::string(HAL_GetSimDeviceName(m_handle));
}
/**
* Creates a value on the simulated device.
*

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

@@ -363,7 +363,7 @@ double HAL_GetEncoderDistancePerPulse(HAL_EncoderHandle encoderHandle,
return 0.0;
}
return encoder->distancePerPulse;
return SimEncoderData[encoder->index].distancePerPulse;
}
HAL_EncoderEncodingType HAL_GetEncoderEncodingType(

View File

@@ -26,6 +26,10 @@ void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) {
SimSimDeviceData->FreeDevice(handle);
}
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle) {
return SimSimDeviceData->GetDeviceName(handle);
}
HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device,
const char* name, int32_t direction,
const struct HAL_Value* initialValue) {

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

@@ -96,6 +96,7 @@ struct TopicData {
NT_Entry entry{0}; // cached entry for GetEntry()
bool onNetwork{false}; // true if there are any remote publishers
bool lastValueFromNetwork{false};
wpi::SmallVector<DataLoggerEntry, 1> datalogs;
NT_Type datalogType{NT_UNASSIGNED};
@@ -484,6 +485,7 @@ void LSImpl::CheckReset(TopicData* topic) {
}
topic->lastValue = {};
topic->lastValueNetwork = {};
topic->lastValueFromNetwork = false;
topic->type = NT_UNASSIGNED;
topic->typeStr.clear();
topic->flags = 0;
@@ -499,10 +501,12 @@ 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;
topic->lastValueFromNetwork = false;
NotifyValue(topic, eventFlags, isDuplicate, publisher);
}
if (!isDuplicate && topic->datalogType == value.type()) {
@@ -858,6 +862,17 @@ SubscriberData* LSImpl::AddLocalSubscriber(TopicData* topic,
DEBUG4("-> NetworkSubscribe({})", topic->name);
m_network->Subscribe(subscriber->handle, {{topic->name}}, config);
}
// queue current value
if (subscriber->active) {
if (!topic->lastValueFromNetwork && !config.disableLocal) {
subscriber->pollStorage.emplace_back(topic->lastValue);
subscriber->handle.Set();
} else if (topic->lastValueFromNetwork && !config.disableRemote) {
subscriber->pollStorage.emplace_back(topic->lastValueNetwork);
subscriber->handle.Set();
}
}
return subscriber;
}
@@ -895,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) {
@@ -906,6 +922,7 @@ MultiSubscriberData* LSImpl::AddMultiSubscriber(
}
}
if (m_network) {
DEBUG4("-> NetworkSubscribe");
m_network->Subscribe(subscriber->handle, subscriber->prefixes,
subscriber->options);
}
@@ -1213,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;
}
@@ -1265,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;
@@ -1376,6 +1394,7 @@ void LocalStorage::NetworkSetValue(NT_Topic topicHandle, const Value& value) {
if (m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE,
value == topic->lastValue, nullptr)) {
topic->lastValueNetwork = value;
topic->lastValueFromNetwork = true;
}
}
}

View File

@@ -215,7 +215,7 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count());
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
}
});
@@ -224,7 +224,7 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count());
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
}
});
m_flushAtomic = m_flush.get();
@@ -355,7 +355,7 @@ NCImpl4::NCImpl4(
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count());
m_clientImpl->SendValues(m_loop.Now().count(), false);
}
});
@@ -364,7 +364,7 @@ NCImpl4::NCImpl4(
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count());
m_clientImpl->SendValues(m_loop.Now().count(), true);
}
});
m_flushAtomic = m_flush.get();
@@ -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);
}
@@ -501,16 +506,15 @@ void NetworkClient::StopDSClient() {
}
void NetworkClient::FlushLocal() {
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) { m_impl->HandleLocal(); });
if (auto async = m_impl->m_flushLocalAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}
void NetworkClient::Flush() {
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) {
m_impl->HandleLocal();
if (m_impl->m_clientImpl) {
m_impl->m_clientImpl->SendValues(m_impl->m_loop.Now().count());
}
});
if (auto async = m_impl->m_flushAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}
class NetworkClient3::Impl final : public NCImpl3 {
@@ -536,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 {
@@ -56,9 +56,9 @@ class CImpl : public ServerMessageHandler {
void ProcessIncomingBinary(std::span<const uint8_t> data);
void HandleLocal(std::vector<ClientMessage>&& msgs);
bool SendControl(uint64_t curTimeMs);
void SendValues(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();
@@ -237,7 +236,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
return true;
}
void CImpl::SendValues(uint64_t curTimeMs) {
void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
DEBUG4("SendValues({})", curTimeMs);
// can't send value updates until we have a RTT
@@ -254,10 +253,11 @@ void CImpl::SendValues(uint64_t curTimeMs) {
bool checkedNetwork = false;
auto writer = m_wire.SendBinary();
for (auto&& pub : m_publishers) {
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
if (pub && !pub->outValues.empty() &&
(flush || curTimeMs >= pub->nextSendMs)) {
for (auto&& val : pub->outValues) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -267,6 +267,10 @@ void CImpl::SendValues(uint64_t curTimeMs) {
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);
@@ -307,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;
}
@@ -474,8 +476,8 @@ void ClientImpl::SendControl(uint64_t curTimeMs) {
m_impl->m_wire.Flush();
}
void ClientImpl::SendValues(uint64_t curTimeMs) {
m_impl->SendValues(curTimeMs);
void ClientImpl::SendValues(uint64_t curTimeMs, bool flush) {
m_impl->SendValues(curTimeMs, flush);
m_impl->m_wire.Flush();
}

View File

@@ -44,7 +44,7 @@ class ClientImpl {
void HandleLocal(std::vector<ClientMessage>&& msgs);
void SendControl(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs, bool flush);
void SetLocal(LocalInterface* local);
void SendInitial();

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 {
@@ -91,9 +91,9 @@ class CImpl : public MessageHandler3 {
void ProcessIncoming(std::span<const uint8_t> data);
void HandleLocal(std::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs, bool initial);
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;
@@ -223,7 +222,7 @@ void CImpl::HandleLocal(std::span<const net::ClientMessage> msgs) {
}
}
void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
DEBUG4("SendPeriodic({})", curTimeMs);
// rate limit sends
@@ -235,7 +234,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
// 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) {
// send any stored-up flags updates
if (!m_outgoingFlags.empty()) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
for (auto&& p : m_outgoingFlags) {
@@ -258,9 +257,10 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
// send any pending updates due to be sent
bool checkedNetwork = false;
for (auto&& pub : m_publishers) {
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
if (pub && !pub->outValues.empty() &&
(flush || curTimeMs >= pub->nextSendMs)) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -301,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;
}
@@ -420,7 +418,7 @@ void CImpl::ServerHelloDone() {
}
// send initial assignments
SendPeriodic(m_initTimeMs, true);
SendPeriodic(m_initTimeMs, true, true);
m_state = kStateRunning;
m_setPeriodic(m_periodMs);
@@ -633,8 +631,8 @@ void ClientImpl3::HandleLocal(std::span<const net::ClientMessage> msgs) {
m_impl->HandleLocal(msgs);
}
void ClientImpl3::SendPeriodic(uint64_t curTimeMs) {
m_impl->SendPeriodic(curTimeMs, false);
void ClientImpl3::SendPeriodic(uint64_t curTimeMs, bool flush) {
m_impl->SendPeriodic(curTimeMs, false, flush);
}
void ClientImpl3::SetLocal(net::LocalInterface* local) {

View File

@@ -38,7 +38,7 @@ class ClientImpl3 {
void ProcessIncoming(std::span<const uint8_t> data);
void HandleLocal(std::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs);
void SendPeriodic(uint64_t curTimeMs, bool flush);
void SetLocal(net::LocalInterface* local);

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

@@ -197,9 +197,6 @@ TEST_F(LocalStorageTest, SubscribeNoTypeLocalPubPre) {
ASSERT_TRUE(value.IsBoolean());
EXPECT_EQ(value.GetBoolean(), true);
EXPECT_EQ(value.time(), 5);
auto vals = storage.ReadQueueValue(sub); // read queue won't get anything
ASSERT_TRUE(vals.empty());
}
TEST_F(LocalStorageTest, EntryNoTypeLocalSet) {
@@ -916,4 +913,48 @@ TEST_F(LocalStorageTest, EntryExcludeSelf) {
ElementsAre(TSEq<TimestampedDouble>(2.0, 60)));
}
TEST_F(LocalStorageTest, ReadQueueInitialLocal) {
EXPECT_CALL(network, Publish(_, _, _, _, _, _));
EXPECT_CALL(network, SetValue(_, _));
EXPECT_CALL(network, Subscribe(_, _, _)).Times(3);
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
auto subBoth =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", kDefaultPubSubOptions);
auto subLocal =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", {.disableRemote = true});
auto subRemote =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", {.disableLocal = true});
EXPECT_THAT(storage.ReadQueueDouble(subBoth),
ElementsAre(TSEq<TimestampedDouble>(1.0, 50)));
EXPECT_THAT(storage.ReadQueueDouble(subLocal),
ElementsAre(TSEq<TimestampedDouble>(1.0, 50)));
EXPECT_THAT(storage.ReadQueueDouble(subRemote), IsEmpty());
}
TEST_F(LocalStorageTest, ReadQueueInitialRemote) {
EXPECT_CALL(network, Subscribe(_, _, _)).Times(3);
auto remoteTopic =
storage.NetworkAnnounce("foo", "double", wpi::json::object(), 0);
storage.NetworkSetValue(remoteTopic, Value::MakeDouble(2.0, 60));
auto subBoth =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", kDefaultPubSubOptions);
auto subLocal =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", {.disableRemote = true});
auto subRemote =
storage.Subscribe(fooTopic, NT_DOUBLE, "double", {.disableLocal = true});
// network set
EXPECT_THAT(storage.ReadQueueDouble(subBoth),
ElementsAre(TSEq<TimestampedDouble>(2.0, 60)));
EXPECT_THAT(storage.ReadQueueDouble(subRemote),
ElementsAre(TSEq<TimestampedDouble>(2.0, 60)));
EXPECT_THAT(storage.ReadQueueDouble(subLocal), IsEmpty());
}
} // namespace nt

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

@@ -1,4 +1,4 @@
def opencvVersion = '4.6.0-2'
def opencvVersion = '4.6.0-3'
if (project.hasProperty('useCpp') && project.useCpp) {
model {

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

@@ -602,7 +602,7 @@ public final class CommandScheduler implements NTSendable, AutoCloseable {
public void requireNotComposed(Command command) {
if (m_composedCommands.contains(command)) {
throw new IllegalArgumentException(
"Commands that have been composed may not be added to another composition or scheduled"
"Commands that have been composed may not be added to another composition or scheduled "
+ "individually!");
}
}
@@ -616,7 +616,7 @@ public final class CommandScheduler implements NTSendable, AutoCloseable {
public void requireNotComposed(Collection<Command> commands) {
if (!Collections.disjoint(commands, getComposedCommands())) {
throw new IllegalArgumentException(
"Commands that have been composed may not be added to another composition or scheduled"
"Commands that have been composed may not be added to another composition or scheduled "
+ "individually!");
}
}

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

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