mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b0eecaee0 | ||
|
|
edf4ded412 | ||
|
|
4c46b6aff9 | ||
|
|
490ca4a68a | ||
|
|
cbb5b0b802 | ||
|
|
bb7053d9ee | ||
|
|
9efed9a533 | ||
|
|
dbbfe1aed2 | ||
|
|
de65a135c3 | ||
|
|
3e9788cdff | ||
|
|
ecb072724d | ||
|
|
0d462a4561 | ||
|
|
ba37986561 | ||
|
|
25ab9cda92 | ||
|
|
2f6251d4a6 | ||
|
|
e9a7bed988 | ||
|
|
9cc14bbb43 | ||
|
|
8068369542 | ||
|
|
805c837a42 | ||
|
|
fd18577ba0 | ||
|
|
74dea9f05e | ||
|
|
9eef79d638 | ||
|
|
843574a810 | ||
|
|
226ef35212 | ||
|
|
b30664d630 | ||
|
|
804e5ce236 | ||
|
|
49af88f2bb | ||
|
|
d56314f866 | ||
|
|
43975ac7cc | ||
|
|
5483464158 | ||
|
|
785e7dd85c | ||
|
|
e57ded8c39 | ||
|
|
01f0394419 | ||
|
|
59be120982 | ||
|
|
37f065032f | ||
|
|
22a170bee7 | ||
|
|
2f310a748c | ||
|
|
b43ec87f57 | ||
|
|
19267bef0c | ||
|
|
84cbd48d84 | ||
|
|
1f35750865 | ||
|
|
8230fc631d | ||
|
|
b879a6f8c6 | ||
|
|
49459d3e45 | ||
|
|
4079eabe9b | ||
|
|
fe5d226a19 | ||
|
|
b7535252c2 | ||
|
|
b61ac6db33 | ||
|
|
7b828ce84f | ||
|
|
08a536291b | ||
|
|
193a10d020 | ||
|
|
7867bbde0e | ||
|
|
fa7c01b598 | ||
|
|
2b81610248 | ||
|
|
a4a369b8da | ||
|
|
d991f6e435 | ||
|
|
a27a047ae8 | ||
|
|
2f96cae31a | ||
|
|
83ef8f9658 | ||
|
|
4054893669 | ||
|
|
f75acd11ce | ||
|
|
8bf67b1b33 | ||
|
|
49bb1358d8 | ||
|
|
9c4c07c0f9 | ||
|
|
1a47cc2e86 | ||
|
|
7cd30cffbc | ||
|
|
92aecab2ef | ||
|
|
8785bba080 | ||
|
|
9e5b7b8040 | ||
|
|
917906530a | ||
|
|
00aa66e4fd | ||
|
|
893320544a | ||
|
|
b95d0e060d | ||
|
|
008232b43c | ||
|
|
522be348f4 | ||
|
|
d48a83dee2 | ||
|
|
504fa22143 | ||
|
|
b2b25bf09f | ||
|
|
ce3dc4eb3b | ||
|
|
1ea48caa7d | ||
|
|
fb101925a7 | ||
|
|
657951f6dd | ||
|
|
a60ca9d71c | ||
|
|
f8a45f1558 | ||
|
|
ecba8b99a8 | ||
|
|
e95e88fdf9 | ||
|
|
371d15dec3 | ||
|
|
cb9b8938af | ||
|
|
3b084ecbe0 | ||
|
|
27ba096ea1 | ||
|
|
42c997a3c4 | ||
|
|
5f1a025f27 | ||
|
|
0ebf79b54c | ||
|
|
a8c465f3fb | ||
|
|
a7b1ab683d | ||
|
|
bd6479dc29 | ||
|
|
5cb0340a8c | ||
|
|
ab0e8c37a7 | ||
|
|
b74ac1c645 |
@@ -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
2
.gitignore
vendored
@@ -9,6 +9,8 @@ simgui-ds.json
|
||||
simgui-window.json
|
||||
simgui.json
|
||||
|
||||
networktables.json
|
||||
|
||||
# Created by the jenkins test script
|
||||
test-reports
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ doxygen {
|
||||
cppIncludeRoots.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
|
||||
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
333
ntcore/src/test/native/cpp/net/ServerImplTest.cpp
Normal file
333
ntcore/src/test/native/cpp/net/ServerImplTest.cpp
Normal 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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
84
processstarter/build.gradle
Normal file
84
processstarter/build.gradle
Normal 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'
|
||||
}
|
||||
67
processstarter/publish.gradle
Normal file
67
processstarter/publish.gradle
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
processstarter/src/main/native/linux/main.cpp
Normal file
86
processstarter/src/main/native/linux/main.cpp
Normal 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);
|
||||
}
|
||||
80
processstarter/src/main/native/osx/main.mm
Normal file
80
processstarter/src/main/native/osx/main.mm
Normal 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;
|
||||
}
|
||||
85
processstarter/src/main/native/windows/main.cpp
Normal file
85
processstarter/src/main/native/windows/main.cpp
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ include 'docs'
|
||||
include 'msvcruntime'
|
||||
include 'ntcoreffi'
|
||||
include 'apriltag'
|
||||
include 'processstarter'
|
||||
|
||||
buildCache {
|
||||
def cred = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
30
upstream_utils/eigen_patches/0002-Intellisense-fix.patch
Normal file
30
upstream_utils/eigen_patches/0002-Intellisense-fix.patch
Normal 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__)
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@ struct SavedSettings {
|
||||
int yPos = -1;
|
||||
int userScale = 2;
|
||||
int style = 0;
|
||||
int fps = 120;
|
||||
};
|
||||
|
||||
constexpr int kFontScaledLevels = 9;
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -210,8 +210,7 @@ public class SwerveControllerCommand extends CommandBase {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
m_timer.reset();
|
||||
m_timer.start();
|
||||
m_timer.restart();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -39,8 +39,7 @@ public class TrapezoidProfileCommand extends CommandBase {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
m_timer.reset();
|
||||
m_timer.start();
|
||||
m_timer.restart();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,8 +30,7 @@ public class WaitCommand extends CommandBase {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
m_timer.reset();
|
||||
m_timer.start();
|
||||
m_timer.restart();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user