Compare commits

...

99 Commits

Author SHA1 Message Date
Colin Wong
4b0eecaee0 [commands] Subsystem: Add default command removal method (#5064) 2023-02-24 19:58:53 -08:00
Noah Andrews
edf4ded412 [wpilib] PH: Revert to 5V rail being fixed 5V (#5122) 2023-02-24 19:55:50 -08:00
Gabor Szita
4c46b6aff9 [wpilibc] Fix DataLogManager crash on exit in sim (#5125) 2023-02-23 20:13:20 -08:00
David Vo
490ca4a68a [wpilibc] Fix XboxController::GetBackButton doc (NFC) (#5131) 2023-02-23 20:11:10 -08:00
superpenguin612
cbb5b0b802 [hal] Simulation: Fix REV PH solenoids 8+ (#5132) 2023-02-23 20:10:44 -08:00
Thad House
bb7053d9ee [hal] Fix HAL_GetRuntimeType being slow on the roboRIO (#5130)
HAL_GetRuntimeType used to be a free call before the roboRIO2 was added. However, nLoadOut::getTargetClass() is not a free call, and it may hit the IPC layer. Cache this value so it is not called every time.
2023-02-23 00:59:59 -08:00
sciencewhiz
9efed9a533 Update .clang-format to c++20 (#5121)
This does not result in any reformatting
2023-02-20 10:54:55 -08:00
sciencewhiz
dbbfe1aed2 [wpilib] Use PH voltage to calc Analog pressure switch threshold (#5115)
The calculated trigger voltages were calculated with a hard coded 5v.
This introduces error when the 5V provided to the Analog pressure
sensor is not exactly 5v, as the pressure is a ratio of the analog
voltage and provided voltage.

This should improve
https://www.chiefdelphi.com/t/rev-pressure-sensor-enablecompressoranalog-not-reaching-configured-pressure/426868
where the 5v voltage was 4.92 volts, which introduces ~8 PSI of error.
2023-02-19 23:13:22 -08:00
Ryan Blue
de65a135c3 [wpilib] DutyCycleEncoderSim: Add channel number constructor (#5118) 2023-02-19 23:12:48 -08:00
sciencewhiz
3e9788cdff [docs] Strip path from generated NT docs (#5119)
Fixes #5117
2023-02-19 23:12:05 -08:00
Peter Johnson
ecb072724d [ntcore] Client::Disconnect(): actually close connection (#5113) 2023-02-17 23:56:49 -08:00
Peter Johnson
0d462a4561 [glass] NT view: Change string/string array to quoted (#5111) 2023-02-17 18:01:54 -08:00
Peter Johnson
ba37986561 [ntcore] NetworkClient::Disconnect: Add null check (#5112) 2023-02-17 16:48:34 -08:00
Peter Johnson
25ab9cda92 [glass,ov] Provide menu item to create topic from root (#5110) 2023-02-17 16:46:02 -08:00
Peter Johnson
2f6251d4a6 [glass] Set default value when publishing new topic (#5109) 2023-02-17 16:45:38 -08:00
Jonah
e9a7bed988 [wpimath] Add timestamp getter to MathShared (#5091)
This makes it possible to mock the timestamp for wpimath without affecting the rest of the library.

Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2023-02-17 14:53:17 -08:00
Peter Johnson
9cc14bbb43 [ntcore] Add stress test to dev executable (#5107) 2023-02-16 22:49:36 -08:00
Peter Johnson
8068369542 [wpinet] uv: Stop creating handles when closing loop (#5102)
This prevents EventLoopRunner::Stop() from hanging in the case when
new handles are created after the async walk closes all the handles.
2023-02-16 22:49:14 -08:00
Peter Johnson
805c837a42 [ntcore] Fix use-after-free in server (#5101)
The client name deduplication didn't properly deduplicate.  Instead,
always append the client index to guarantee a unique name.
2023-02-16 22:45:50 -08:00
bovlb
fd18577ba0 [commands] Improve documentation of addRequirements (NFC) (#5103) 2023-02-16 22:08:46 -08:00
Tyler Veness
74dea9f05e [wpimath] Fix exception for empty pose buffer in pose estimators (#5106)
Fixes #5100.
2023-02-16 22:00:21 -08:00
sciencewhiz
9eef79d638 [wpilib] PneumaticHub: Document range of enableCompressorAnalog (NFC) (#5099) 2023-02-15 20:25:12 -08:00
Peter Johnson
843574a810 [ntcore] Use wpi::Now instead of loop time for transmit time
As the send function is called after local processing, there can be a
substantial delay between the loop time and the actual send.
2023-02-13 23:00:03 -08:00
Peter Johnson
226ef35212 [wpinet] WebSocket: Reduce server send frame overhead
Avoid allocating 4K buffer to send a 10-byte header per frame.
2023-02-13 23:00:03 -08:00
Peter Johnson
b30664d630 [ntcore] Reduce initial connection overhead
Mixing the announce and value messages causes significant downstream
inefficiency in both time and space.
2023-02-13 23:00:03 -08:00
sciencewhiz
804e5ce236 [examples] MecanumDrive: Fix axis comment in C++ example (NFC) (#5096) 2023-02-13 22:18:23 -08:00
Starlight220
49af88f2bb [examples] ArmSimulation: Fix flaky test (#5093) 2023-02-13 12:59:27 -08:00
Peter Johnson
d56314f866 [wpiutil] Disable mock time on the Rio (#5092) 2023-02-12 22:38:34 -08:00
Starlight220
43975ac7cc [examples] ArmSimulation, ElevatorSimulation: Extract mechanism to class (#5052) 2023-02-12 06:50:57 -08:00
Starlight220
5483464158 [examples, templates] Improve descriptions (NFC) (#5051) 2023-02-12 06:49:20 -08:00
Starlight220
785e7dd85c [wpilibc] SendableChooser: static_assert copy- and default-constructibility (#5078)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-02-12 06:24:00 -08:00
Peter Johnson
e57ded8c39 [ntcore] Improve disconnect error reporting (#5085)
Also fix memory leak in WebSocketConnection destructor.
2023-02-11 22:56:29 -08:00
Peter Johnson
01f0394419 [wpinet] Revert WebSocket: When Close() is called, call closed immediately (#5084)
This caused crashes in ntcore.

This reverts commit b879a6f8c6 (#5047).
2023-02-11 22:56:01 -08:00
Jordan McMichael
59be120982 [wpimath] Fix Pose3d exp()/log() and add rotation vector constructor to Rotation3d (#5072)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2023-02-08 21:31:03 -08:00
Tyler Veness
37f065032f [wpilib] Refactor TimedRobot tests (#5068) 2023-02-07 23:00:46 -08:00
Ryan Blue
22a170bee7 [wpilib] Add Notifier test (#5070) 2023-02-07 23:00:17 -08:00
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
304 changed files with 7093 additions and 3229 deletions

View File

@@ -156,7 +156,7 @@ SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: c++17
Standard: c++20
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,6 +64,7 @@ doxygen {
cppIncludeRoots.add(it.absolutePath)
}
}
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
if (project.hasProperty('docWarningsAsErrors')) {
// C++20 shims

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -400,6 +400,11 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
}
} else {
valueChildren.clear();
valueStr.clear();
wpi::raw_string_ostream os{valueStr};
os << '"';
os.write_escaped(value.GetString());
os << '"';
}
break;
case NT_RAW:
@@ -769,25 +774,32 @@ static bool StringToStringArray(std::string_view in,
}
in = wpi::trim(in);
wpi::SmallVector<std::string_view, 16> inSplit;
wpi::SmallString<32> buf;
wpi::split(in, inSplit, ',', -1, false);
for (auto val : inSplit) {
val = wpi::trim(val);
if (val.empty()) {
continue;
}
if (val.front() != '"' || val.back() != '"') {
fmt::print(stderr,
"GUI: NetworkTables: Could not understand value '{}'\n", val);
while (!in.empty()) {
if (in.front() != '"') {
fmt::print(stderr, "GUI: NetworkTables: Expected '\"'");
return false;
}
val.remove_prefix(1);
val.remove_suffix(1);
out->emplace_back(wpi::UnescapeCString(val, buf).first);
in.remove_prefix(1);
wpi::SmallString<128> buf;
std::string_view val;
std::tie(val, in) = wpi::UnescapeCString(in, buf);
out->emplace_back(val);
if (!in.empty()) {
if (in.front() != '"') {
fmt::print(stderr, "GUI: NetworkTables: Error escaping string");
return false;
}
in.remove_prefix(1);
in = wpi::ltrim(in);
}
if (!in.empty()) {
if (in.front() != ',') {
fmt::print(stderr, "GUI: NetworkTables: Expected ','");
return false;
}
in.remove_prefix(1);
}
}
return true;
}
@@ -826,9 +838,8 @@ static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
break;
}
case NT_STRING: {
// GetString() comes from a std::string, so it's null terminated
ImGui::LabelText(typeStr ? typeStr : "string", "%s",
val.GetString().data());
entry.valueStr.c_str());
break;
}
case NT_BOOLEAN_ARRAY:
@@ -938,13 +949,18 @@ static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry,
break;
}
case NT_STRING: {
char* v = GetTextBuffer(val.GetString());
char* v = GetTextBuffer(entry.valueStr);
if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
ImGuiInputTextFlags_EnterReturnsTrue)) {
if (entry.publisher == 0) {
entry.publisher = nt::Publish(entry.info.topic, NT_STRING, "string");
if (v[0] == '"') {
if (entry.publisher == 0) {
entry.publisher =
nt::Publish(entry.info.topic, NT_STRING, "string");
}
wpi::SmallString<128> buf;
nt::SetString(entry.publisher,
wpi::UnescapeCString(v + 1, buf).first);
}
nt::SetString(entry.publisher, v);
}
break;
}
@@ -1045,58 +1061,97 @@ static void CreateTopicMenuItem(NetworkTablesModel* model,
model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
if (entry->publisher == 0) {
entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
// publish a default value so it's editable
switch (type) {
case NT_BOOLEAN:
nt::SetDefaultBoolean(entry->publisher, false);
break;
case NT_INTEGER:
nt::SetDefaultInteger(entry->publisher, 0);
break;
case NT_FLOAT:
nt::SetDefaultFloat(entry->publisher, 0.0);
break;
case NT_DOUBLE:
nt::SetDefaultDouble(entry->publisher, 0.0);
break;
case NT_STRING:
nt::SetDefaultString(entry->publisher, "");
break;
case NT_BOOLEAN_ARRAY:
nt::SetDefaultBooleanArray(entry->publisher, {});
break;
case NT_INTEGER_ARRAY:
nt::SetDefaultIntegerArray(entry->publisher, {});
break;
case NT_FLOAT_ARRAY:
nt::SetDefaultFloatArray(entry->publisher, {});
break;
case NT_DOUBLE_ARRAY:
nt::SetDefaultDoubleArray(entry->publisher, {});
break;
case NT_STRING_ARRAY:
nt::SetDefaultStringArray(entry->publisher, {});
break;
default:
break;
}
}
}
}
void glass::DisplayNetworkTablesAddMenu(NetworkTablesModel* model,
std::string_view path,
NetworkTablesFlags flags) {
static char nameBuffer[kTextBufferSize];
if (ImGui::BeginMenu("Add new...")) {
if (ImGui::IsWindowAppearing()) {
nameBuffer[0] = '\0';
}
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
kTextBufferSize);
std::string fullNewPath;
if (path == "/") {
path = "";
}
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
ImGui::Text("Adding: %s", fullNewPath.c_str());
ImGui::Separator();
auto entry = model->GetEntry(fullNewPath);
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
nameBuffer[0] != '\0') &&
!exists;
CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
enabled);
ImGui::EndMenu();
}
}
static void EmitParentContextMenu(NetworkTablesModel* model,
const std::string& path,
NetworkTablesFlags flags) {
static char nameBuffer[kTextBufferSize];
if (ImGui::BeginPopupContextItem(path.c_str())) {
ImGui::Text("%s", path.c_str());
ImGui::Separator();
if (ImGui::BeginMenu("Add new...")) {
if (ImGui::IsWindowAppearing()) {
nameBuffer[0] = '\0';
}
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
kTextBufferSize);
std::string fullNewPath;
if (path == "/") {
fullNewPath = path + nameBuffer;
} else {
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
}
ImGui::Text("Adding: %s", fullNewPath.c_str());
ImGui::Separator();
auto entry = model->GetEntry(fullNewPath);
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
nameBuffer[0] != '\0') &&
!exists;
CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
enabled);
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
enabled);
ImGui::EndMenu();
}
DisplayNetworkTablesAddMenu(model, path, flags);
ImGui::EndPopup();
}
@@ -1280,7 +1335,6 @@ static void DisplayTable(NetworkTablesModel* model,
}
ImGui::TableHeadersRow();
// EmitParentContextMenu(model, "/", flags);
if (flags & NetworkTablesFlags_TreeView) {
switch (category) {
case ShowPersistent:
@@ -1511,6 +1565,7 @@ void NetworkTablesView::Display() {
void NetworkTablesView::Settings() {
m_flags.DisplayMenu();
DisplayNetworkTablesAddMenu(m_model, {}, m_flags.GetFlags());
}
bool NetworkTablesView::HasSettings() {

View File

@@ -194,6 +194,10 @@ void DisplayNetworkTables(
NetworkTablesModel* model,
NetworkTablesFlags flags = NetworkTablesFlags_Default);
void DisplayNetworkTablesAddMenu(
NetworkTablesModel* model, std::string_view path = {},
NetworkTablesFlags flags = NetworkTablesFlags_Default);
class NetworkTablesFlagsSettings {
public:
explicit NetworkTablesFlagsSettings(

View File

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

View File

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

View File

@@ -50,6 +50,7 @@ using namespace hal;
namespace hal {
void InitializeDriverStation();
void WaitForInitialPacket();
namespace init {
void InitializeHAL() {
InitializeCTREPCM();
@@ -252,12 +253,10 @@ const char* HAL_GetErrorMessage(int32_t code) {
}
}
static HAL_RuntimeType runtimeType = HAL_Runtime_RoboRIO;
HAL_RuntimeType HAL_GetRuntimeType(void) {
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
return HAL_Runtime_RoboRIO2;
}
return HAL_Runtime_RoboRIO;
return runtimeType;
}
int32_t HAL_GetFPGAVersion(int32_t* status) {
@@ -522,6 +521,13 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
return false;
}
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
runtimeType = HAL_Runtime_RoboRIO2;
} else {
runtimeType = HAL_Runtime_RoboRIO;
}
InterruptManager::Initialize(global->getSystemInterface());
hal::InitializeDriverStation();
@@ -546,6 +552,8 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
return rv;
});
hal::WaitForInitialPacket();
initialized = true;
return true;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -201,7 +201,7 @@ int32_t HAL_GetREVPHSolenoids(HAL_REVPHHandle handle, int32_t* status) {
std::scoped_lock lock{pcm->lock};
auto& data = SimREVPHData[pcm->module].solenoidOutput;
uint8_t ret = 0;
int32_t ret = 0;
for (int i = 0; i < kNumREVPHChannels; i++) {
ret |= (data[i] << i);
}

View File

@@ -7,6 +7,7 @@
#include <cmath>
#include <cstdlib>
#include <numeric>
#include <random>
#include <string_view>
#include <thread>
@@ -17,12 +18,18 @@
#include "ntcore_cpp.h"
void bench();
void stress();
int main(int argc, char* argv[]) {
if (argc == 2 && std::string_view{argv[1]} == "bench") {
bench();
return EXIT_SUCCESS;
}
if (argc == 2 && std::string_view{argv[1]} == "stress") {
stress();
return EXIT_SUCCESS;
}
auto myValue = nt::GetEntry(nt::GetDefaultInstance(), "MyValue");
nt::SetEntryValue(myValue, nt::Value::MakeString("Hello World"));
@@ -106,3 +113,72 @@ void bench() {
fmt::print("-- Flush --\n");
PrintTimes(flushTimes);
}
static std::random_device r;
static std::mt19937 gen(r());
static std::uniform_real_distribution<double> dist;
void stress() {
auto server = nt::CreateInstance();
nt::StartServer(server, "stress.json", "127.0.0.1", 0, 10000);
nt::SubscribeMultiple(server, {{std::string_view{}}});
using namespace std::chrono_literals;
for (int count = 0; count < 10; ++count) {
std::thread{[] {
auto client = nt::CreateInstance();
nt::SubscribeMultiple(client, {{std::string_view{}}});
for (int i = 0; i < 300; ++i) {
// sleep a random amount of time
std::this_thread::sleep_for(0.1s * dist(gen));
// connect
nt::StartClient4(client, "client");
nt::SetServer(client, "127.0.0.1", 10000);
// sleep a random amount of time
std::this_thread::sleep_for(0.1s * dist(gen));
// disconnect
nt::StopClient(client);
}
nt::DestroyInstance(client);
}}.detach();
std::thread{[server, count] {
for (int n = 0; n < 300; ++n) {
// sleep a random amount of time
std::this_thread::sleep_for(0.01s * dist(gen));
// create publishers
NT_Publisher pub[30];
for (int i = 0; i < 30; ++i) {
pub[i] =
nt::Publish(nt::GetTopic(server, fmt::format("{}_{}", count, i)),
NT_DOUBLE, "double", {});
}
// publish values
for (int i = 0; i < 200; ++i) {
// sleep a random amount of time between each value set
std::this_thread::sleep_for(0.001s * dist(gen));
for (int i = 0; i < 30; ++i) {
nt::SetDouble(pub[i], dist(gen));
}
nt::FlushLocal(server);
}
// sleep a random amount of time
std::this_thread::sleep_for(0.1s * dist(gen));
// remove publishers
for (int i = 0; i < 30; ++i) {
nt::Unpublish(pub[i]);
}
}
}}.detach();
}
std::this_thread::sleep_for(100s);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,7 @@ class NCImpl {
void StopDSClient();
virtual void TcpConnected(uv::Tcp& tcp) = 0;
virtual void ForceDisconnect(std::string_view reason) = 0;
virtual void Disconnect(std::string_view reason);
// invariants
@@ -99,6 +100,7 @@ class NCImpl3 : public NCImpl {
void HandleLocal();
void TcpConnected(uv::Tcp& tcp) final;
void ForceDisconnect(std::string_view reason) override;
void Disconnect(std::string_view reason) override;
std::shared_ptr<net3::UvStreamConnection3> m_wire;
@@ -117,6 +119,7 @@ class NCImpl4 : public NCImpl {
void HandleLocal();
void TcpConnected(uv::Tcp& tcp) final;
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
void ForceDisconnect(std::string_view reason) override;
void Disconnect(std::string_view reason) override;
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
@@ -155,7 +158,9 @@ void NCImpl::SetServers(
[this, servers = std::move(serversCopy)](uv::Loop&) mutable {
m_servers = std::move(servers);
if (m_dsClientServer.first.empty()) {
m_parallelConnect->SetServers(m_servers);
if (m_parallelConnect) {
m_parallelConnect->SetServers(m_servers);
}
}
});
}
@@ -167,14 +172,20 @@ void NCImpl::StartDSClient(unsigned int port) {
}
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT4 : port;
m_dsClient = wpi::DsClient::Create(m_loop, m_logger);
m_dsClient->setIp.connect([this](std::string_view ip) {
m_dsClientServer.first = ip;
m_parallelConnect->SetServers({{m_dsClientServer}});
});
m_dsClient->clearIp.connect([this] {
m_dsClientServer.first.clear();
m_parallelConnect->SetServers(m_servers);
});
if (m_dsClient) {
m_dsClient->setIp.connect([this](std::string_view ip) {
m_dsClientServer.first = ip;
if (m_parallelConnect) {
m_parallelConnect->SetServers({{m_dsClientServer}});
}
});
m_dsClient->clearIp.connect([this] {
m_dsClientServer.first.clear();
if (m_parallelConnect) {
m_parallelConnect->SetServers(m_servers);
}
});
}
});
}
@@ -191,15 +202,20 @@ void NCImpl::Disconnect(std::string_view reason) {
if (m_readLocalTimer) {
m_readLocalTimer->Stop();
}
m_sendValuesTimer->Stop();
if (m_sendValuesTimer) {
m_sendValuesTimer->Stop();
}
m_localStorage.ClearNetwork();
m_localQueue.ClearQueue();
m_connList.RemoveConnection(m_connHandle);
m_connHandle = 0;
// start trying to connect again
uv::Timer::SingleShot(m_loop, kReconnectRate,
[this] { m_parallelConnect->Disconnected(); });
uv::Timer::SingleShot(m_loop, kReconnectRate, [this] {
if (m_parallelConnect) {
m_parallelConnect->Disconnected();
}
});
}
NCImpl3::NCImpl3(int inst, std::string_view id,
@@ -212,25 +228,31 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
m_sendValuesTimer = uv::Timer::Create(loop);
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
}
});
if (m_sendValuesTimer) {
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
}
});
}
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
}
});
if (m_flush) {
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
}
});
}
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
if (m_flushLocal) {
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
}
m_flushLocalAtomic = m_flushLocal.get();
});
}
@@ -261,8 +283,10 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
auto clientImpl = std::make_shared<net3::ClientImpl3>(
m_loop.Now().count(), m_inst, *wire, m_logger, [this](uint32_t repeatMs) {
DEBUG4("Setting periodic timer to {}", repeatMs);
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
if (m_sendValuesTimer) {
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
}
});
clientImpl->Start(
m_id, [this, wire,
@@ -276,7 +300,9 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
return;
}
m_parallelConnect->Succeeded(tcp);
if (m_parallelConnect) {
m_parallelConnect->Succeeded(tcp);
}
m_wire = std::move(wire);
m_clientImpl = std::move(clientImpl);
@@ -305,7 +331,7 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
tcp.closed.connect([this, &tcp] {
DEBUG3("NT3 TCP connection closed");
if (!tcp.IsLoopClosing()) {
Disconnect(m_wire->GetDisconnectReason());
Disconnect(m_wire ? m_wire->GetDisconnectReason() : "unknown");
}
});
@@ -323,6 +349,12 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
tcp.StartRead();
}
void NCImpl3::ForceDisconnect(std::string_view reason) {
if (m_wire) {
m_wire->Disconnect(reason);
}
}
void NCImpl3::Disconnect(std::string_view reason) {
INFO("DISCONNECTED NT3 connection: {}", reason);
m_clientImpl.reset();
@@ -343,34 +375,42 @@ NCImpl4::NCImpl4(
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
m_readLocalTimer = uv::Timer::Create(loop);
m_readLocalTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendControl(m_loop.Now().count());
}
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
if (m_readLocalTimer) {
m_readLocalTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendControl(m_loop.Now().count());
}
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
}
m_sendValuesTimer = uv::Timer::Create(loop);
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count(), false);
}
});
if (m_sendValuesTimer) {
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count(), false);
}
});
}
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count(), true);
}
});
if (m_flush) {
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count(), true);
}
});
}
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
if (m_flushLocal) {
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
}
m_flushLocalAtomic = m_flushLocal.get();
});
}
@@ -418,7 +458,9 @@ void NCImpl4::TcpConnected(uv::Tcp& tcp) {
}
void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
m_parallelConnect->Succeeded(tcp);
if (m_parallelConnect) {
m_parallelConnect->Succeeded(tcp);
}
ConnectionInfo connInfo;
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip, &connInfo.remote_port);
@@ -432,8 +474,10 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
m_loop.Now().count(), m_inst, *m_wire, m_logger, m_timeSyncUpdated,
[this](uint32_t repeatMs) {
DEBUG4("Setting periodic timer to {}", repeatMs);
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
if (m_sendValuesTimer) {
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
}
});
m_clientImpl->SetLocal(&m_localStorage);
m_localStorage.StartNetwork(&m_localQueue);
@@ -456,8 +500,19 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
});
}
void NCImpl4::ForceDisconnect(std::string_view reason) {
if (m_wire) {
m_wire->Disconnect(reason);
}
}
void NCImpl4::Disconnect(std::string_view reason) {
INFO("DISCONNECTED NT4 connection: {}", reason);
std::string realReason;
if (m_wire) {
realReason = m_wire->GetDisconnectReason();
}
INFO("DISCONNECTED NT4 connection: {}",
realReason.empty() ? reason : realReason);
m_clientImpl.reset();
m_wire.reset();
NCImpl::Disconnect(reason);
@@ -492,6 +547,11 @@ void NetworkClient::SetServers(
m_impl->SetServers(servers, NT_DEFAULT_PORT4);
}
void NetworkClient::Disconnect() {
m_impl->m_loopRunner.ExecAsync(
[this](auto&) { m_impl->ForceDisconnect("requested by application"); });
}
void NetworkClient::StartDSClient(unsigned int port) {
m_impl->StartDSClient(port);
}
@@ -535,6 +595,11 @@ void NetworkClient3::SetServers(
m_impl->SetServers(servers, NT_DEFAULT_PORT3);
}
void NetworkClient3::Disconnect() {
m_impl->m_loopRunner.ExecAsync(
[this](auto&) { m_impl->ForceDisconnect("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

@@ -255,8 +255,9 @@ void ServerConnection4::ProcessWsUpgrade() {
m_info.remote_id = dedupName;
m_server.AddConnection(this, m_info);
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
auto realReason = m_wire->GetDisconnectReason();
INFO("DISCONNECTED NT4 client '{}' (from {}): {}", m_info.remote_id,
m_connInfo, reason);
m_connInfo, realReason.empty() ? reason : realReason);
ConnectionClosed();
});
m_websocket->text.connect([this](std::string_view data, bool) {
@@ -360,8 +361,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);
@@ -411,36 +420,46 @@ void NSImpl::Init() {
// set up timers
m_readLocalTimer = uv::Timer::Create(m_loop);
m_readLocalTimer->timeout.connect([this] {
HandleLocal();
m_serverImpl.SendControl(m_loop.Now().count());
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
if (m_readLocalTimer) {
m_readLocalTimer->timeout.connect([this] {
HandleLocal();
m_serverImpl.SendControl(m_loop.Now().count());
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
}
m_savePersistentTimer = uv::Timer::Create(m_loop);
m_savePersistentTimer->timeout.connect([this] {
if (m_serverImpl.PersistentChanged()) {
uv::QueueWork(
m_loop,
[this, fn = m_persistentFilename,
data = m_serverImpl.DumpPersistent()] { SavePersistent(fn, data); },
nullptr);
}
});
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
if (m_savePersistentTimer) {
m_savePersistentTimer->timeout.connect([this] {
if (m_serverImpl.PersistentChanged()) {
uv::QueueWork(
m_loop,
[this, fn = m_persistentFilename,
data = m_serverImpl.DumpPersistent()] {
SavePersistent(fn, data);
},
nullptr);
}
});
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
}
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
HandleLocal();
for (auto&& conn : m_connections) {
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
}
});
if (m_flush) {
m_flush->wakeup.connect([this] {
HandleLocal();
for (auto&& conn : m_connections) {
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
}
});
}
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
if (m_flushLocal) {
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
}
m_flushLocalAtomic = m_flushLocal.get();
INFO("Listening on NT3 port {}, NT4 port {}", m_port3, m_port4);

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 kWireMaxNotReadyUs = 1000000;
namespace {
@@ -58,7 +58,7 @@ class CImpl : public ServerMessageHandler {
bool SendControl(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs, bool flush);
void SendInitialValues();
bool CheckNetworkReady();
bool CheckNetworkReady(uint64_t curTimeMs);
// ServerMessageHandler interface
void ServerAnnounce(std::string_view name, int64_t id,
@@ -98,7 +98,6 @@ class CImpl : public ServerMessageHandler {
// periodic sweep handling
uint32_t m_periodMs{kPingIntervalMs + 10};
uint64_t m_lastSendMs{0};
int m_notReadyCount{0};
// outgoing queue
std::vector<ClientMessage> m_outgoing;
@@ -208,7 +207,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
// start a timestamp RTT ping if it's time to do one
if (curTimeMs >= m_nextPingTimeMs) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return false;
}
auto now = wpi::Now();
@@ -219,7 +218,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
}
if (!m_outgoing.empty()) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return false;
}
auto writer = m_wire.SendText();
@@ -258,7 +257,7 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
(flush || curTimeMs >= pub->nextSendMs)) {
for (auto&& val : pub->outValues) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -268,6 +267,10 @@ void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
int64_t time = val.time();
if (time != 0) {
time += m_serverTimeOffsetUs;
// make sure resultant time isn't exactly 0
if (time == 0) {
time = 1;
}
}
WireEncodeBinary(writer.Add(), Handle{pub->handle}.GetIndex(), time,
val);
@@ -308,15 +311,15 @@ void CImpl::SendInitialValues() {
}
}
bool CImpl::CheckNetworkReady() {
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}

View File

@@ -48,9 +48,9 @@ using namespace mpack;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
namespace {
@@ -78,12 +78,10 @@ class SImpl;
class ClientData {
public:
ClientData(std::string_view originalName, std::string_view name,
std::string_view connInfo, bool local,
ClientData(std::string_view name, std::string_view connInfo, bool local,
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
wpi::Logger& logger)
: m_originalName{originalName},
m_name{name},
: m_name{name},
m_connInfo{connInfo},
m_local{local},
m_setPeriodic{std::move(setPeriodic)},
@@ -114,12 +112,10 @@ class ClientData {
std::string_view name, bool special,
wpi::SmallVectorImpl<SubscriberData*>& buf);
std::string_view GetOriginalName() const { return m_originalName; }
std::string_view GetName() const { return m_name; }
int GetId() const { return m_id; }
protected:
std::string m_originalName;
std::string m_name;
std::string m_connInfo;
bool m_local; // local to machine
@@ -143,12 +139,10 @@ class ClientData {
class ClientData4Base : public ClientData, protected ClientMessageHandler {
public:
ClientData4Base(std::string_view originalName, std::string_view name,
std::string_view connInfo, bool local,
ClientData4Base(std::string_view name, std::string_view connInfo, bool local,
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server,
int id, wpi::Logger& logger)
: ClientData{originalName, name, connInfo, local,
setPeriodic, server, id, logger} {}
: ClientData{name, connInfo, local, setPeriodic, server, id, logger} {}
protected:
// ClientMessageHandler interface
@@ -170,8 +164,7 @@ class ClientData4Base : public ClientData, protected ClientMessageHandler {
class ClientDataLocal final : public ClientData4Base {
public:
ClientDataLocal(SImpl& server, int id, wpi::Logger& logger)
: ClientData4Base{"", "", "", true, [](uint32_t) {}, server, id, logger} {
}
: ClientData4Base{"", "", true, [](uint32_t) {}, server, id, logger} {}
void ProcessIncomingText(std::string_view data) final {}
void ProcessIncomingBinary(std::span<const uint8_t> data) final {}
@@ -189,12 +182,10 @@ class ClientDataLocal final : public ClientData4Base {
class ClientData4 final : public ClientData4Base {
public:
ClientData4(std::string_view originalName, std::string_view name,
std::string_view connInfo, bool local, WireConnection& wire,
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
wpi::Logger& logger)
: ClientData4Base{originalName, name, connInfo, local,
setPeriodic, server, id, logger},
ClientData4(std::string_view name, std::string_view connInfo, bool local,
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic,
SImpl& server, int id, wpi::Logger& logger)
: ClientData4Base{name, connInfo, local, setPeriodic, server, id, logger},
m_wire{wire} {}
void ProcessIncomingText(std::string_view data) final;
@@ -214,7 +205,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);
@@ -247,7 +237,7 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
net3::WireConnection3& wire, ServerImpl::Connected3Func connected,
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
wpi::Logger& logger)
: ClientData{"", "", connInfo, local, setPeriodic, server, id, logger},
: ClientData{"", connInfo, local, setPeriodic, server, id, logger},
m_connected{std::move(connected)},
m_wire{wire},
m_decoder{*this} {}
@@ -293,7 +283,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 +590,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);
@@ -648,6 +641,11 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
}
// see if this immediately subscribes to any topics
// for transmit efficiency, we want to batch announcements and values, so
// send announcements in first loop and remember what we want to send in
// second loop.
std::vector<TopicData*> dataToSend;
dataToSend.reserve(m_server.m_topics.size());
for (auto&& topic : m_server.m_topics) {
bool removed = false;
if (replace) {
@@ -656,10 +654,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,17 +674,22 @@ 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) {
dataToSend.emplace_back(topic.get());
}
}
for (auto topic : dataToSend) {
DEBUG4("send last value for {} to client {}", topic->name, m_id);
SendValue(topic, topic->lastValue, kSendAll);
}
// update meta data
@@ -937,13 +943,13 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
}
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
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 +1116,13 @@ void ClientData3::SendOutgoing(uint64_t curTimeMs) {
}
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
m_wire.Disconnect("transmit stalled");
}
return;
}
m_notReadyCount = 0;
auto out = m_wire.Send();
for (auto&& msg : m_outgoing) {
@@ -1501,39 +1507,29 @@ SImpl::SImpl(wpi::Logger& logger) : m_logger{logger} {
std::pair<std::string, int> SImpl::AddClient(
std::string_view name, std::string_view connInfo, bool local,
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic) {
// strip anything after @ in the name
name = wpi::split(name, '@').first;
if (name.empty()) {
name = "NT4";
}
size_t index = m_clients.size();
// find an empty slot and check for duplicates
// find an empty slot
// just do a linear search as number of clients is typically small (<10)
int duplicateName = 0;
for (size_t i = 0, end = index; i < end; ++i) {
auto& clientData = m_clients[i];
if (clientData && clientData->GetOriginalName() == name) {
++duplicateName;
} else if (!clientData && index == end) {
if (!m_clients[i]) {
index = i;
break;
}
}
if (index == m_clients.size()) {
m_clients.emplace_back();
}
// if duplicate name, de-duplicate
std::string dedupName;
if (duplicateName > 0) {
dedupName = fmt::format("{}@{}", name, duplicateName);
} else {
dedupName = name;
}
// ensure name is unique by suffixing index
std::string dedupName = fmt::format("{}@{}", name, index);
auto& clientData = m_clients[index];
clientData = std::make_unique<ClientData4>(name, dedupName, connInfo, local,
wire, std::move(setPeriodic),
*this, index, m_logger);
clientData = std::make_unique<ClientData4>(dedupName, connInfo, local, wire,
std::move(setPeriodic), *this,
index, m_logger);
// create client meta topics
clientData->m_metaPub =
@@ -2125,7 +2121,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;
@@ -2287,9 +2283,10 @@ void ServerImpl::SendControl(uint64_t curTimeMs) {
}
void ServerImpl::SendValues(int clientId, uint64_t curTimeMs) {
auto client = m_impl->m_clients[clientId].get();
client->SendOutgoing(curTimeMs);
client->Flush();
if (auto client = m_impl->m_clients[clientId].get()) {
client->SendOutgoing(curTimeMs);
client->Flush();
}
}
void ServerImpl::HandleLocal(std::span<const ClientMessage> msgs) {

View File

@@ -7,6 +7,7 @@
#include <span>
#include <wpi/SpanExtras.h>
#include <wpi/timestamp.h>
#include <wpinet/WebSocket.h>
using namespace nt;
@@ -25,6 +26,12 @@ WebSocketConnection::~WebSocketConnection() {
for (auto&& buf : m_buf_pool) {
buf.Deallocate();
}
for (auto&& buf : m_text_buffers) {
buf.Deallocate();
}
for (auto&& buf : m_binary_buffers) {
buf.Deallocate();
}
}
void WebSocketConnection::Flush() {
@@ -50,6 +57,10 @@ void WebSocketConnection::Flush() {
if (self->m_sendsActive > 0) {
--self->m_sendsActive;
}
} else {
for (auto&& buf : bufs) {
buf.Deallocate();
}
}
});
m_frames.clear();
@@ -57,9 +68,11 @@ void WebSocketConnection::Flush() {
m_binary_buffers.clear();
m_text_pos = 0;
m_binary_pos = 0;
m_lastFlushTime = wpi::Now();
}
void WebSocketConnection::Disconnect(std::string_view reason) {
m_reason = reason;
m_ws.Close(1005, reason);
}

View File

@@ -5,6 +5,8 @@
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/SmallVector.h>
@@ -32,8 +34,12 @@ class WebSocketConnection final
void Flush() final;
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
void Disconnect(std::string_view reason) final;
std::string_view GetDisconnectReason() const { return m_reason; }
private:
void StartSendText() final;
void FinishSendText() final;
@@ -64,6 +70,8 @@ class WebSocketConnection final
size_t m_binary_pos = 0;
bool m_in_text = false;
int m_sendsActive = 0;
std::string m_reason;
uint64_t m_lastFlushTime = 0;
};
} // namespace nt::net

View File

@@ -30,6 +30,8 @@ class WireConnection {
virtual void Flush() = 0;
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
virtual void Disconnect(std::string_view reason) = 0;
protected:

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

@@ -13,6 +13,7 @@
#include <wpi/DenseMap.h>
#include <wpi/StringMap.h>
#include <wpi/json.h>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Log.h"
@@ -31,9 +32,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 kWireMaxNotReadyUs = 1000000;
namespace {
@@ -93,7 +94,7 @@ class CImpl : public MessageHandler3 {
void HandleLocal(std::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs, bool initial, bool flush);
void SendValue(Writer& out, Entry* entry, const Value& value);
bool CheckNetworkReady();
bool CheckNetworkReady(uint64_t curTimeMs);
// Outgoing handlers
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
@@ -142,7 +143,6 @@ class CImpl : public MessageHandler3 {
uint32_t m_periodMs{kKeepAliveIntervalMs + 10};
uint64_t m_lastSendMs{0};
uint64_t m_nextKeepAliveTimeMs;
int m_notReadyCount{0};
// indexed by publisher index
std::vector<std::unique_ptr<PublisherData>> m_publishers;
@@ -235,7 +235,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
// send keep-alives
if (curTimeMs >= m_nextKeepAliveTimeMs) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
DEBUG4("Sending keep alive");
@@ -246,7 +246,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
// send any stored-up flags updates
if (!m_outgoingFlags.empty()) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
for (auto&& p : m_outgoingFlags) {
@@ -261,7 +261,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
if (pub && !pub->outValues.empty() &&
(flush || curTimeMs >= pub->nextSendMs)) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
@@ -302,15 +302,15 @@ 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) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}

View File

@@ -4,6 +4,7 @@
#include "UvStreamConnection3.h"
#include <wpi/timestamp.h>
#include <wpinet/uv/Stream.h>
using namespace nt;
@@ -33,6 +34,7 @@ void UvStreamConnection3::Flush() {
});
m_buffers.clear();
m_os.reset();
m_lastFlushTime = wpi::Now();
}
void UvStreamConnection3::Disconnect(std::string_view reason) {

View File

@@ -38,6 +38,8 @@ class UvStreamConnection3 final
void Flush() final;
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
void Disconnect(std::string_view reason) final;
std::string_view GetDisconnectReason() const { return m_reason; }
@@ -54,6 +56,7 @@ class UvStreamConnection3 final
std::vector<wpi::uv::Buffer> m_buf_pool;
wpi::raw_uv_ostream m_os;
std::string m_reason;
uint64_t m_lastFlushTime = 0;
int m_sendsActive = 0;
};

View File

@@ -26,6 +26,8 @@ class WireConnection3 {
virtual void Flush() = 0;
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
virtual void Disconnect(std::string_view reason) = 0;
protected:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,8 @@ class MockWireConnection : public WireConnection {
MOCK_METHOD(void, Flush, (), (override));
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
protected:

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@1");
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@1");
EXPECT_NE(id1, -1);
EXPECT_EQ(name2, "test@2");
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

@@ -28,6 +28,8 @@ class MockWireConnection3 : public WireConnection3 {
MOCK_METHOD(void, Flush, (), (override));
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
protected:

View File

@@ -119,6 +119,7 @@ static void DisplayGui() {
gui::EmitViewMenu();
if (ImGui::BeginMenu("View")) {
gFlagsSettings.DisplayMenu();
glass::DisplayNetworkTablesAddMenu(gModel.get());
ImGui::EndMenu();
}
@@ -183,6 +184,8 @@ static void DisplayGui() {
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate,
ImGui::GetIO().Framerate);
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}

View File

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

View File

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

View File

@@ -0,0 +1,86 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <poll.h>
#include <spawn.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <climits>
#include <cstdio>
#include <cstring>
#include <filesystem>
int main(int argc, char* argv[]) {
char path[PATH_MAX];
char dest[PATH_MAX];
std::memset(dest, 0, sizeof(dest)); // readlink does not null terminate!
pid_t pid = getpid();
std::snprintf(path, PATH_MAX, "/proc/%d/exe", pid);
int readlink_len = readlink(path, dest, PATH_MAX);
if (readlink_len < 0) {
std::perror("readlink");
return 1;
} else if (readlink_len == PATH_MAX) {
std::printf("Truncation occured\n");
return 1;
}
std::filesystem::path exePath{dest};
if (exePath.empty()) {
return 1;
}
if (!exePath.has_stem()) {
return 1;
}
if (!exePath.has_parent_path()) {
return 1;
}
std::filesystem::path jarPath{exePath};
jarPath.replace_extension("jar");
std::filesystem::path parentPath{exePath.parent_path()};
if (!parentPath.has_parent_path()) {
return 1;
}
std::filesystem::path toolsFolder{parentPath.parent_path()};
std::filesystem::path Java = toolsFolder / "jdk" / "bin" / "java";
pid = 0;
std::string data = jarPath;
std::string jarArg = "-jar";
char* const arguments[] = {jarArg.data(), data.data(), nullptr};
int status =
posix_spawn(&pid, Java.c_str(), nullptr, nullptr, arguments, environ);
if (status != 0) {
char* home = std::getenv("JAVA_HOME");
std::string javaLocal = "java";
if (home != nullptr) {
std::filesystem::path javaHomePath{home};
javaHomePath /= "bin";
javaHomePath /= "java";
javaLocal = javaHomePath;
}
status = posix_spawn(&pid, javaLocal.c_str(), nullptr, nullptr, arguments,
environ);
if (status != 0) {
return 1;
}
}
int childPid = syscall(SYS_pidfd_open, pid, 0);
if (childPid <= 0) {
return 1;
}
struct pollfd pfd = {childPid, POLLIN, 0};
return poll(&pfd, 1, 3000);
}

View File

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

View File

@@ -0,0 +1,85 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <filesystem>
#include "Windows.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR pCmdLine, int nCmdShow) {
DWORD Status;
WCHAR ExePathRaw[1024];
Status = GetModuleFileNameW(NULL, ExePathRaw, 1024);
if (Status == 0) {
DWORD LastError = GetLastError();
return LastError;
}
std::filesystem::path ExePath{ExePathRaw};
if (ExePath.empty()) {
return 1;
}
if (!ExePath.has_stem()) {
return 1;
}
if (!ExePath.has_parent_path()) {
return 1;
}
std::filesystem::path JarPath{ExePath};
JarPath.replace_extension(L"jar");
std::filesystem::path ParentPath{ExePath.parent_path()};
if (!ParentPath.has_parent_path()) {
return 1;
}
std::filesystem::path ToolsFolder{ParentPath.parent_path()};
std::filesystem::path Javaw = ToolsFolder / L"jdk" / L"bin" / L"javaw.exe";
std::wstring ToRun = L" -jar \"";
ToRun += JarPath;
ToRun += L"\"";
STARTUPINFOW StartupInfo;
PROCESS_INFORMATION ProcessInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
if (!CreateProcessW(Javaw.c_str(), ToRun.data(), NULL, NULL, FALSE, 0, NULL,
NULL, &StartupInfo, &ProcessInfo)) {
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
ToRun = L" -jar \"";
ToRun += JarPath;
ToRun += L"\"";
Status = GetEnvironmentVariableW(L"JAVA_HOME", ExePathRaw, 1024);
std::wstring JavawLocal = L"javaw";
if (Status != 0 && Status < 1024) {
std::filesystem::path JavaHomePath{ExePathRaw};
JavaHomePath /= "bin";
JavaHomePath /= "javaw.exe";
JavawLocal = JavaHomePath;
}
if (!CreateProcessW(JavawLocal.c_str(), ToRun.data(), NULL, NULL, FALSE, 0,
NULL, NULL, &StartupInfo, &ProcessInfo)) {
return 1;
}
}
Status =
WaitForSingleObject(ProcessInfo.hProcess, 3000); // Wait for 3 seconds
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
return Status == WAIT_TIMEOUT ? 0 : 1;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,11 @@ public abstract class CommandBase implements Sendable, Command {
}
/**
* Adds the specified requirements to the command.
* Adds the specified subsystems to the requirements of the command. The scheduler will prevent
* two commands that require the same subsystem from being scheduled simultaneously.
*
* <p>Note that the scheduler determines the requirements of a command when it is scheduled, so
* this method should normally be called from the command's constructor.
*
* @param requirements the requirements to add
*/

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

@@ -50,6 +50,14 @@ public interface Subsystem {
CommandScheduler.getInstance().setDefaultCommand(this, defaultCommand);
}
/**
* Removes the default command for the subsystem. This will not cancel the default command if it
* is currently running.
*/
default void removeDefaultCommand() {
CommandScheduler.getInstance().removeDefaultCommand(this);
}
/**
* Gets the default command for this subsystem. Returns null if no default command is currently
* associated with the subsystem.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,10 @@ void Subsystem::SetDefaultCommand(CommandPtr&& defaultCommand) {
std::move(defaultCommand));
}
void Subsystem::RemoveDefaultCommand() {
CommandScheduler::GetInstance().RemoveDefaultCommand(this);
}
Command* Subsystem::GetDefaultCommand() const {
return CommandScheduler::GetInstance().GetDefaultCommand(this);
}

View File

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

View File

@@ -28,6 +28,13 @@ class CommandBase : public Command,
/**
* Adds the specified Subsystem requirements to the command.
*
* The scheduler will prevent two commands that require the same subsystem
* from being scheduled simultaneously.
*
* Note that the scheduler determines the requirements of a command when it
* is scheduled, so this method should normally be called from the command's
* constructor.
*
* @param requirements the Subsystem requirements to add
*/
void AddRequirements(std::initializer_list<Subsystem*> requirements);
@@ -35,6 +42,13 @@ class CommandBase : public Command,
/**
* Adds the specified Subsystem requirements to the command.
*
* The scheduler will prevent two commands that require the same subsystem
* from being scheduled simultaneously.
*
* Note that the scheduler determines the requirements of a command when it
* is scheduled, so this method should normally be called from the command's
* constructor.
*
* @param requirements the Subsystem requirements to add
*/
void AddRequirements(std::span<Subsystem* const> requirements);
@@ -42,6 +56,13 @@ class CommandBase : public Command,
/**
* Adds the specified Subsystem requirements to the command.
*
* The scheduler will prevent two commands that require the same subsystem
* from being scheduled simultaneously.
*
* Note that the scheduler determines the requirements of a command when it
* is scheduled, so this method should normally be called from the command's
* constructor.
*
* @param requirements the Subsystem requirements to add
*/
void AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements);
@@ -49,6 +70,13 @@ class CommandBase : public Command,
/**
* Adds the specified Subsystem requirement to the command.
*
* The scheduler will prevent two commands that require the same subsystem
* from being scheduled simultaneously.
*
* Note that the scheduler determines the requirements of a command when it
* is scheduled, so this method should normally be called from the command's
* constructor.
*
* @param requirement the Subsystem requirement to add
*/
void AddRequirements(Subsystem* requirement);

View File

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

View File

@@ -83,6 +83,12 @@ class Subsystem {
*/
void SetDefaultCommand(CommandPtr&& defaultCommand);
/**
* Removes the default command for the subsystem. This will not cancel the
* default command if it is currently running.
*/
void RemoveDefaultCommand();
/**
* Gets the default command for this subsystem. Returns null if no default
* command is currently associated with the subsystem.

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