mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa34aacf6e | ||
|
|
63512bbbb8 | ||
|
|
9227b2166e | ||
|
|
fbf92e9190 | ||
|
|
2108a61362 | ||
|
|
0a66479693 | ||
|
|
b510c17ef6 | ||
|
|
e7a7eb2e93 | ||
|
|
a465f2d8f0 | ||
|
|
a3364422fa | ||
|
|
df3242a40a | ||
|
|
00abb8c1e0 | ||
|
|
c886273fd7 | ||
|
|
53b5fd2ace | ||
|
|
56b758320f | ||
|
|
08f298e4cd | ||
|
|
6d0c5b19db | ||
|
|
0d22cf5ff7 | ||
|
|
32ec5b3f75 | ||
|
|
e5c4c6b1a7 | ||
|
|
099d048d9e | ||
|
|
4af84a1c12 | ||
|
|
ce3686b80d | ||
|
|
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 | ||
|
|
cf1a411acf | ||
|
|
1e05b21ab5 | ||
|
|
e5a6197633 | ||
|
|
039edcc23f | ||
|
|
f7f19207e0 | ||
|
|
befd12911c | ||
|
|
34519de60a | ||
|
|
dc4355c031 | ||
|
|
53d8d33bca | ||
|
|
530ae40614 | ||
|
|
79f565191e | ||
|
|
2cd9be413f | ||
|
|
babb0c1fcf | ||
|
|
330ba45f9c | ||
|
|
51272ef6b3 | ||
|
|
0d105ab771 | ||
|
|
cf4235ea36 | ||
|
|
2d4b7b9147 | ||
|
|
aec6f3d506 | ||
|
|
bfe346c76a |
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2022 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2023 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -16,7 +16,7 @@ We provide two base types of artifacts.
|
||||
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier.
|
||||
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respecively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifer combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respectively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifier combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
## Artifact Names
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,7 +40,7 @@ if (WITH_JAVA)
|
||||
set(CMAKE_JAVA_INCLUDE_PATH apriltag.jar ${EJML_JARS} ${JACKSON_JARS})
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/main/native/resources/*.json)
|
||||
add_jar(apriltag_jar
|
||||
SOURCES ${JAVA_SOURCES}
|
||||
RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES}
|
||||
|
||||
@@ -37,6 +37,9 @@ import java.util.Optional;
|
||||
* at the bottom-right corner of the blue alliance wall. {@link #setOrigin(OriginPosition)} can be
|
||||
* used to change the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be from the
|
||||
* perspective of a specific alliance.
|
||||
*
|
||||
* <p>Tag poses represent the center of the tag, with a zero rotation representing a tag that is
|
||||
* upright and facing away from the (blue) alliance wall (that is, towards the opposing alliance).
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@@ -186,7 +189,10 @@ public class AprilTagFieldLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a field layout from a resource within a jar file.
|
||||
* Deserializes a field layout from a resource within a internal jar file.
|
||||
*
|
||||
* <p>Users should use {@link AprilTagFields#loadAprilTagLayoutField()} to load official layouts
|
||||
* and {@link #AprilTagFieldLayout(String)} for custom layouts.
|
||||
*
|
||||
* @param resourcePath The absolute path of the resource
|
||||
* @return The deserialized layout
|
||||
|
||||
@@ -4,17 +4,30 @@
|
||||
|
||||
package edu.wpi.first.apriltag;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public enum AprilTagFields {
|
||||
k2022RapidReact("2022-rapidreact.json");
|
||||
k2022RapidReact("2022-rapidreact.json"),
|
||||
k2023ChargedUp("2023-chargedup.json");
|
||||
|
||||
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
|
||||
|
||||
/** Alias to the current game. */
|
||||
public static final AprilTagFields kDefaultField = k2022RapidReact;
|
||||
public static final AprilTagFields kDefaultField = k2023ChargedUp;
|
||||
|
||||
public final String m_resourceFile;
|
||||
|
||||
AprilTagFields(String resourceFile) {
|
||||
m_resourceFile = kBaseResourceDir + resourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link AprilTagFieldLayout} from the resource JSON.
|
||||
*
|
||||
* @return AprilTagFieldLayout of the field
|
||||
* @throws IOException If the layout does not exist
|
||||
*/
|
||||
public AprilTagFieldLayout loadAprilTagLayoutField() throws IOException {
|
||||
return AprilTagFieldLayout.loadFromResource(m_resourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace frc {
|
||||
|
||||
// C++ generated from resource files
|
||||
std::string_view GetResource_2022_rapidreact_json();
|
||||
std::string_view GetResource_2023_chargedup_json();
|
||||
|
||||
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
|
||||
std::string_view fieldString;
|
||||
@@ -17,6 +18,9 @@ AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
|
||||
case AprilTagField::k2022RapidReact:
|
||||
fieldString = GetResource_2022_rapidreact_json();
|
||||
break;
|
||||
case AprilTagField::k2023ChargedUp:
|
||||
fieldString = GetResource_2023_chargedup_json();
|
||||
break;
|
||||
case AprilTagField::kNumFields:
|
||||
throw std::invalid_argument("Invalid Field");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@ namespace frc {
|
||||
* Pose3ds in the JSON are measured using the normal FRC coordinate system, NWU
|
||||
* with the origin at the bottom-right corner of the blue alliance wall.
|
||||
* SetOrigin(OriginPosition) can be used to change the poses returned from
|
||||
* GetTagPose(int) to be from the perspective of a specific alliance. */
|
||||
* GetTagPose(int) to be from the perspective of a specific alliance.
|
||||
*
|
||||
* Tag poses represent the center of the tag, with a zero rotation representing
|
||||
* a tag that is upright and facing away from the (blue) alliance wall (that is,
|
||||
* towards the opposing alliance). */
|
||||
class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
public:
|
||||
enum class OriginPosition {
|
||||
@@ -75,7 +79,7 @@ class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
/**
|
||||
* Sets the origin for tag pose transformation.
|
||||
*
|
||||
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* This transforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* correct pose relative to the provided origin.
|
||||
*
|
||||
* @param origin The new origin for tag transformations
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace frc {
|
||||
|
||||
enum class AprilTagField {
|
||||
k2022RapidReact,
|
||||
k2023ChargedUp,
|
||||
|
||||
// This is a placeholder for denoting the last supported field. This should
|
||||
// always be the last entry in the enum and should not be used by users
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"tags": [
|
||||
{
|
||||
"ID": 1,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 15.513558,
|
||||
"y": 1.071626,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 2,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 15.513558,
|
||||
"y": 2.748026,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 3,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 15.513558,
|
||||
"y": 4.424426,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 4,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 16.178784,
|
||||
"y": 6.749796,
|
||||
"z": 0.695452
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 0.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 5,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 0.36195,
|
||||
"y": 6.749796,
|
||||
"z": 0.695452
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 6,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.02743,
|
||||
"y": 4.424426,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 7,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.02743,
|
||||
"y": 2.748026,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": 8,
|
||||
"pose": {
|
||||
"translation": {
|
||||
"x": 1.02743,
|
||||
"y": 1.071626,
|
||||
"z": 0.462788
|
||||
},
|
||||
"rotation": {
|
||||
"quaternion": {
|
||||
"W": 1.0,
|
||||
"X": 0.0,
|
||||
"Y": 0.0,
|
||||
"Z": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"field": {
|
||||
"length": 16.54175,
|
||||
"width": 8.0137
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -143,7 +143,7 @@ public class CameraServerJNI {
|
||||
public static native void releaseSource(int source);
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
public static native void setCameraBrightness(int source, int brightness);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
namespace cs {
|
||||
|
||||
// The UnlimitedHandleResource class is a way to track handles. This version
|
||||
// allows an unlimted number of handles that are allocated sequentially. When
|
||||
// allows an unlimited number of handles that are allocated sequentially. When
|
||||
// possible, indices are reused to save memory usage and keep the array length
|
||||
// down.
|
||||
// However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -439,7 +439,7 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
|
||||
@@ -323,7 +323,7 @@ void CS_ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void CS_SetCameraBrightness(CS_Source source, int brightness,
|
||||
|
||||
@@ -262,7 +262,7 @@ void ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status);
|
||||
|
||||
@@ -79,7 +79,7 @@ using namespace cs;
|
||||
default:
|
||||
OBJCERROR(
|
||||
"Camera access explicitly blocked for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
self.isAuthorized = false;
|
||||
// TODO log
|
||||
break;
|
||||
@@ -524,7 +524,7 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
if (!self.isAuthorized) {
|
||||
OBJCERROR(
|
||||
"Camera access not authorized for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
return S_OK;
|
||||
if (SUCCEEDED(hrStatus)) {
|
||||
if (pSample) {
|
||||
// Prcoess sample
|
||||
// Process sample
|
||||
source->ProcessFrame(pSample, m_mode);
|
||||
// DO NOT release the frame
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class UsbCameraTest {
|
||||
static class ConnectVerbose {
|
||||
@Test
|
||||
void setConnectVerboseEnabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(1);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
@@ -38,7 +38,7 @@ class UsbCameraTest {
|
||||
|
||||
@Test
|
||||
void setConnectVerboseDisabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(0);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -466,7 +466,8 @@ static void ValueToCsv(wpi::raw_ostream& os, const Entry& entry,
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "int64") {
|
||||
} else if (entry.type == "int64" || entry.type == "int") {
|
||||
// support "int" for compatibility with old NT4 datalogs
|
||||
int64_t val;
|
||||
if (record.GetInteger(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
|
||||
@@ -64,6 +64,7 @@ doxygen {
|
||||
cppIncludeRoots.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
|
||||
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
@@ -238,7 +239,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
if (JavaVersion.current().isJava8Compatible() && project.hasProperty('docWarningsAsErrors')) {
|
||||
// Treat javadoc warnings as errors.
|
||||
//
|
||||
// The second argument '-quiet' is a hack. The one paramater
|
||||
// The second argument '-quiet' is a hack. The one parameter
|
||||
// addStringOption() doesn't work, so we add '-quiet', which is added
|
||||
// anyway by gradle. See https://github.com/gradle/gradle/issues/2354.
|
||||
//
|
||||
|
||||
@@ -151,7 +151,7 @@ html {
|
||||
--side-nav-arrow-opacity: 0;
|
||||
--side-nav-arrow-hover-opacity: 0.9;
|
||||
|
||||
/* height of an item in any tree / collapsable table */
|
||||
/* height of an item in any tree / collapsible table */
|
||||
--tree-item-height: 30px;
|
||||
|
||||
--darkmode-toggle-button-icon: "☀️";
|
||||
@@ -1615,7 +1615,7 @@ SOFTWARE.
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enought to contain the page title (logo + title + version)
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 340px;
|
||||
--menu-display: none;
|
||||
|
||||
@@ -11,7 +11,7 @@ if (WITH_JAVA)
|
||||
set(CMAKE_JAVA_INCLUDE_PATH fieldImages.jar ${JACKSON_JARS})
|
||||
|
||||
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json src/main/native/resources/*.png src/main/native/resources/*.jpg)
|
||||
file(GLOB_RECURSE JAVA_RESOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} src/main/native/resources/*.json src/main/native/resources/*.png src/main/native/resources/*.jpg)
|
||||
add_jar(field_images_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/fields" ${JAVA_RESOURCES} OUTPUT_NAME fieldImages)
|
||||
|
||||
get_property(FIELD_IMAGES_JAR_FILE TARGET field_images_jar PROPERTY JAR_FILE)
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FieldConfig {
|
||||
*
|
||||
* @param resourcePath The path to the resource file
|
||||
* @return The field configuration
|
||||
* @throws IOException Throws if the resoure could not be loaded
|
||||
* @throws IOException Throws if the resource could not be loaded
|
||||
*/
|
||||
public static FieldConfig loadFromResource(String resourcePath) throws IOException {
|
||||
try (InputStream stream = FieldConfig.class.getResourceAsStream(resourcePath);
|
||||
|
||||
@@ -14,12 +14,13 @@ public enum Fields {
|
||||
k2021GalacticSearchA("2021-galacticsearcha.json"),
|
||||
k2021GalacticSearchB("2021-galacticsearchb.json"),
|
||||
k2021Slalom("2021-slalompath.json"),
|
||||
k2022RapidReact("2022-rapidreact.json");
|
||||
k2022RapidReact("2022-rapidreact.json"),
|
||||
k2023ChargedUp("2023-chargedup.json");
|
||||
|
||||
public static final String kBaseResourceDir = "/edu/wpi/first/fields/";
|
||||
|
||||
/** Alias to the current game. */
|
||||
public static final Fields kDefaultField = k2022RapidReact;
|
||||
public static final Fields kDefaultField = k2023ChargedUp;
|
||||
|
||||
public final String m_resourceFile;
|
||||
|
||||
|
||||
12
fieldImages/src/main/native/include/fields/2023-chargedup.h
Normal file
12
fieldImages/src/main/native/include/fields/2023-chargedup.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fields {
|
||||
std::string_view GetResource_2023_chargedup_json();
|
||||
std::string_view GetResource_2023_field_png();
|
||||
} // namespace fields
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"game": "Charged Up",
|
||||
"field-image": "2023-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
46,
|
||||
36
|
||||
],
|
||||
"bottom-right": [
|
||||
1088,
|
||||
544
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54.27083,
|
||||
26.2916
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -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);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class SimDevice implements AutoCloseable {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. Returns null if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>null is returned if not in simulation.
|
||||
@@ -106,6 +106,15 @@ public class SimDevice implements AutoCloseable {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the simulated device.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return SimDeviceJNI.getSimDeviceName(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a value on the simulated device.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. 0 is returned if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>0 is returned if not in simulation.
|
||||
@@ -33,6 +33,14 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
*/
|
||||
public static native void freeSimDevice(int handle);
|
||||
|
||||
/**
|
||||
* Get the name of a simulated device.
|
||||
*
|
||||
* @param handle simulated device handle
|
||||
* @return name of the simulated device
|
||||
*/
|
||||
public static native String getSimDeviceName(int handle);
|
||||
|
||||
private static native int createSimValueNative(
|
||||
int device, String name, int direction, int type, long value1, double value2);
|
||||
|
||||
|
||||
@@ -79,6 +79,15 @@ public class EncoderDataJNI extends JNIWrapper {
|
||||
|
||||
public static native void setSamplesToAverage(int index, int samplesToAverage);
|
||||
|
||||
public static native int registerDistancePerPulseCallback(
|
||||
int index, NotifyCallback callback, boolean initialNotify);
|
||||
|
||||
public static native void cancelDistancePerPulseCallback(int index, int uid);
|
||||
|
||||
public static native double getDistancePerPulse(int index);
|
||||
|
||||
public static native void setDistancePerPulse(int index, double distancePerPulse);
|
||||
|
||||
public static native void setDistance(int index, double distance);
|
||||
|
||||
public static native double getDistance(int index);
|
||||
|
||||
@@ -69,7 +69,7 @@ void setAnalogNumChannelsToActivate(int32_t channels);
|
||||
* number of active channels and the sample rate.
|
||||
*
|
||||
* When the number of channels changes, use the new value. Otherwise,
|
||||
* return the curent value.
|
||||
* return the current value.
|
||||
*
|
||||
* @return Value to write to the active channels field.
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ int32_t HAL_GetFPGAEncoder(HAL_FPGAEncoderHandle fpgaEncoderHandle,
|
||||
/**
|
||||
* Returns the period of the most recent pulse.
|
||||
* Returns the period of the most recent Encoder pulse in seconds.
|
||||
* This method compenstates for the decoding type.
|
||||
* This method compensates for the decoding type.
|
||||
*
|
||||
* @deprecated Use GetRate() in favor of this method. This returns unscaled
|
||||
* periods and GetRate() scales using value from SetDistancePerPulse().
|
||||
|
||||
@@ -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) {
|
||||
@@ -317,11 +316,8 @@ void InitializeRoboRioComments(void) {
|
||||
return;
|
||||
}
|
||||
start += searchString.size();
|
||||
size_t end = fileContents.find("\"", start);
|
||||
if (end == std::string_view::npos) {
|
||||
end = fileContents.size();
|
||||
}
|
||||
std::string_view escapedComments = wpi::slice(fileContents, start, end);
|
||||
std::string_view escapedComments =
|
||||
wpi::slice(fileContents, start, fileContents.size());
|
||||
wpi::SmallString<64> buf;
|
||||
auto [unescapedComments, rem] = wpi::UnescapeCString(escapedComments, buf);
|
||||
unescapedComments.copy(roboRioCommentsString,
|
||||
@@ -522,6 +518,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 +549,8 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return rv;
|
||||
});
|
||||
|
||||
hal::WaitForInitialPacket();
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name) {
|
||||
|
||||
void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) {}
|
||||
|
||||
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle) {
|
||||
return "";
|
||||
}
|
||||
|
||||
HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device,
|
||||
const char* name, int32_t direction,
|
||||
const struct HAL_Value* initialValue) {
|
||||
|
||||
@@ -51,7 +51,7 @@ struct DeviceDescriptor
|
||||
// Bootloader version. Will not change for the life of the product, but additional
|
||||
// field upgrade features could be added in newer hardware.
|
||||
char bootloaderRev[MAX_STRING_LEN];
|
||||
// Manufacture Date. Could be a calender date or just the FRC season year.
|
||||
// Manufacture Date. Could be a calendar date or just the FRC season year.
|
||||
// Also helps troubleshooting "old ones" vs "new ones".
|
||||
char manufactureDate[MAX_STRING_LEN];
|
||||
// General status of the hardware. For example if the device is in bootloader
|
||||
|
||||
@@ -79,7 +79,7 @@ extern "C" {
|
||||
/**
|
||||
* Signals in message Compressor_Config.
|
||||
*
|
||||
* Configures compressor to use digitial/analog sensors
|
||||
* Configures compressor to use digital/analog sensors
|
||||
*
|
||||
* All signal values are as on the CAN bus.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -63,6 +63,18 @@ Java_edu_wpi_first_hal_SimDeviceJNI_freeSimDevice
|
||||
HAL_FreeSimDevice(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: getSimDeviceName
|
||||
* Signature: (I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_edu_wpi_first_hal_SimDeviceJNI_getSimDeviceName
|
||||
(JNIEnv* env, jclass, jint handle)
|
||||
{
|
||||
return MakeJString(env, HAL_GetSimDeviceName(handle));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: createSimValueNative
|
||||
|
||||
@@ -412,6 +412,56 @@ Java_edu_wpi_first_hal_simulation_EncoderDataJNI_setSamplesToAverage
|
||||
HALSIM_SetEncoderSamplesToAverage(index, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
|
||||
* Method: registerDistancePerPulseCallback
|
||||
* Signature: (ILjava/lang/Object;Z)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_registerDistancePerPulseCallback
|
||||
(JNIEnv* env, jclass, jint index, jobject callback, jboolean initialNotify)
|
||||
{
|
||||
return sim::AllocateCallback(env, index, callback, initialNotify,
|
||||
&HALSIM_RegisterEncoderDistancePerPulseCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
|
||||
* Method: cancelDistancePerPulseCallback
|
||||
* Signature: (II)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_cancelDistancePerPulseCallback
|
||||
(JNIEnv* env, jclass, jint index, jint handle)
|
||||
{
|
||||
return sim::FreeCallback(env, handle, index,
|
||||
&HALSIM_CancelEncoderDistancePerPulseCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
|
||||
* Method: getDistancePerPulse
|
||||
* Signature: (I)D
|
||||
*/
|
||||
JNIEXPORT jdouble JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_getDistancePerPulse
|
||||
(JNIEnv*, jclass, jint index)
|
||||
{
|
||||
return HALSIM_GetEncoderDistancePerPulse(index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
|
||||
* Method: setDistancePerPulse
|
||||
* Signature: (ID)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_EncoderDataJNI_setDistancePerPulse
|
||||
(JNIEnv*, jclass, jint index, jdouble value)
|
||||
{
|
||||
HALSIM_SetEncoderDistancePerPulse(index, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_EncoderDataJNI
|
||||
* Method: setDistance
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -40,7 +40,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes an object for peforming DMA transfers.
|
||||
* Initializes an object for performing DMA transfers.
|
||||
*
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created dma handle
|
||||
@@ -310,7 +310,7 @@ enum HAL_DMAReadStatus HAL_ReadDMADirect(void* dmaPointer,
|
||||
* timing out
|
||||
* @param[in] remainingOut the number of samples remaining in the queue
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the succes result of the sample read
|
||||
* @return the success result of the sample read
|
||||
*/
|
||||
enum HAL_DMAReadStatus HAL_ReadDMA(HAL_DMAHandle handle,
|
||||
HAL_DMASample* dmaSample,
|
||||
|
||||
@@ -28,7 +28,7 @@ extern "C" {
|
||||
* @param errorCode the error code
|
||||
* @param isLVCode true for a LV error code, false for a standard error code
|
||||
* @param details the details of the error
|
||||
* @param location the file location of the errror
|
||||
* @param location the file location of the error
|
||||
* @param callStack the callstack of the error
|
||||
* @param printMsg true to print the error message to stdout as well as to the
|
||||
* DS
|
||||
@@ -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);
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* Expected to be called internally, not by users.
|
||||
*
|
||||
* @param library the library path
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadOneExtension(const char* library);
|
||||
|
||||
@@ -39,7 +39,7 @@ int HAL_LoadOneExtension(const char* library);
|
||||
* Loads any extra halsim libraries provided in the HALSIM_EXTENSIONS
|
||||
* environment variable.
|
||||
*
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadExtensions(void);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ extern "C" {
|
||||
* Initializes a Power Distribution Panel.
|
||||
*
|
||||
* @param[in] moduleNumber the module number to initialize
|
||||
* @param[in] type the type of module to intialize
|
||||
* @param[in] type the type of module to initialize
|
||||
* @param[in] allocationLocation the location where the allocation is occurring
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created PowerDistribution
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifdef __cplusplus
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
#include "hal/Types.h"
|
||||
@@ -46,7 +47,7 @@ extern "C" {
|
||||
*
|
||||
* The device name must be unique. 0 is returned if the device name already
|
||||
* exists. If multiple instances of the same device are desired, recommend
|
||||
* appending the instance/unique identifer in brackets to the base name,
|
||||
* appending the instance/unique identifier in brackets to the base name,
|
||||
* e.g. "device[1]".
|
||||
*
|
||||
* 0 is returned if not in simulation.
|
||||
@@ -66,6 +67,14 @@ HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name);
|
||||
*/
|
||||
void HAL_FreeSimDevice(HAL_SimDeviceHandle handle);
|
||||
|
||||
/**
|
||||
* Get the name of a simulated device
|
||||
*
|
||||
* @param handle simulated device handle
|
||||
* @return name of the simulated device
|
||||
*/
|
||||
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle);
|
||||
|
||||
/**
|
||||
* Creates a value on a simulated device.
|
||||
*
|
||||
@@ -654,7 +663,7 @@ class SimDevice {
|
||||
*
|
||||
* The device name must be unique. Returns null if the device name
|
||||
* already exists. If multiple instances of the same device are desired,
|
||||
* recommend appending the instance/unique identifer in brackets to the base
|
||||
* recommend appending the instance/unique identifier in brackets to the base
|
||||
* name, e.g. "device[1]".
|
||||
*
|
||||
* If not in simulation, results in an "empty" object that evaluates to false
|
||||
@@ -731,6 +740,15 @@ class SimDevice {
|
||||
*/
|
||||
operator HAL_SimDeviceHandle() const { return m_handle; } // NOLINT
|
||||
|
||||
/**
|
||||
* Get the name of the simulated device.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
std::string GetName() const {
|
||||
return std::string(HAL_GetSimDeviceName(m_handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a value on the simulated device.
|
||||
*
|
||||
|
||||
@@ -63,8 +63,8 @@ int32_t ComputeDigitalMask(HAL_DigitalHandle handle, int32_t* status);
|
||||
|
||||
/**
|
||||
* Unsafe digital output set function
|
||||
* This function can be used to perform fast and determinstically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anyting other then
|
||||
* This function can be used to perform fast and deterministically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anything other then
|
||||
* functions on the Proxy object passed as a parameter can deadlock your
|
||||
* program.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace hal {
|
||||
|
||||
/**
|
||||
* The UnlimitedHandleResource class is a way to track handles. This version
|
||||
* allows an unlimted number of handles that are allocated sequentially. When
|
||||
* allows an unlimited number of handles that are allocated sequentially. When
|
||||
* possible, indices are reused to save memory usage and keep the array length
|
||||
* down.
|
||||
* However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -363,7 +363,7 @@ double HAL_GetEncoderDistancePerPulse(HAL_EncoderHandle encoderHandle,
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return encoder->distancePerPulse;
|
||||
return SimEncoderData[encoder->index].distancePerPulse;
|
||||
}
|
||||
|
||||
HAL_EncoderEncodingType HAL_GetEncoderEncodingType(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) {
|
||||
SimSimDeviceData->FreeDevice(handle);
|
||||
}
|
||||
|
||||
const char* HAL_GetSimDeviceName(HAL_SimDeviceHandle handle) {
|
||||
return SimSimDeviceData->GetDeviceName(handle);
|
||||
}
|
||||
|
||||
HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device,
|
||||
const char* name, int32_t direction,
|
||||
const struct HAL_Value* initialValue) {
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
@@ -17,12 +19,23 @@
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
void bench();
|
||||
void bench2();
|
||||
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]} == "bench2") {
|
||||
bench2();
|
||||
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 +119,145 @@ void bench() {
|
||||
fmt::print("-- Flush --\n");
|
||||
PrintTimes(flushTimes);
|
||||
}
|
||||
|
||||
void bench2() {
|
||||
// set up instances
|
||||
auto client1 = nt::CreateInstance();
|
||||
auto client2 = nt::CreateInstance();
|
||||
auto server = nt::CreateInstance();
|
||||
|
||||
// connect client and server
|
||||
nt::StartServer(server, "bench2.json", "127.0.0.1", 10001, 10000);
|
||||
nt::StartClient4(client1, "client1");
|
||||
nt::StartClient3(client2, "client2");
|
||||
nt::SetServer(client1, "127.0.0.1", 10000);
|
||||
nt::SetServer(client2, "127.0.0.1", 10001);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
// add "typical" set of subscribers on client and server
|
||||
nt::SubscribeMultiple(client1, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(client2, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(server, {{std::string_view{}}});
|
||||
|
||||
// create 1000 entries
|
||||
std::array<NT_Entry, 1000> pubs;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pubs[i] = nt::GetEntry(
|
||||
nt::GetTopic(server,
|
||||
fmt::format("/some/long/name/with/lots/of/slashes/{}", i)),
|
||||
NT_DOUBLE_ARRAY, "double[]");
|
||||
}
|
||||
|
||||
// warm up
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
nt::FlushLocal(server);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
}
|
||||
|
||||
std::vector<int64_t> flushTimes;
|
||||
flushTimes.reserve(1001);
|
||||
|
||||
std::vector<int64_t> times;
|
||||
times.reserve(1001);
|
||||
|
||||
// benchmark
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
int64_t now = nt::Now();
|
||||
for (int i = 1; i <= 1000; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
int64_t prev = now;
|
||||
now = nt::Now();
|
||||
times.emplace_back(now - prev);
|
||||
nt::FlushLocal(server);
|
||||
nt::Flush(server);
|
||||
flushTimes.emplace_back(nt::Now() - now);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
now = nt::Now();
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
|
||||
fmt::print("total time: {}us\n",
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
|
||||
.count());
|
||||
PrintTimes(times);
|
||||
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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class NetworkTableListener implements AutoCloseable {
|
||||
* Create a time synchronization listener.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param immediateNotify notify listener of current time synchonization value
|
||||
* @param immediateNotify notify listener of current time synchronization value
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -96,6 +96,7 @@ struct TopicData {
|
||||
NT_Entry entry{0}; // cached entry for GetEntry()
|
||||
|
||||
bool onNetwork{false}; // true if there are any remote publishers
|
||||
bool lastValueFromNetwork{false};
|
||||
|
||||
wpi::SmallVector<DataLoggerEntry, 1> datalogs;
|
||||
NT_Type datalogType{NT_UNASSIGNED};
|
||||
@@ -242,7 +243,7 @@ struct DataLoggerData {
|
||||
int Start(TopicData* topic, int64_t time) {
|
||||
return log.Start(fmt::format("{}{}", logPrefix,
|
||||
wpi::drop_front(topic->name, prefix.size())),
|
||||
topic->typeStr,
|
||||
topic->typeStr == "int" ? "int64" : topic->typeStr,
|
||||
DataLoggerEntry::MakeMetadata(topic->propertiesStr), time);
|
||||
}
|
||||
|
||||
@@ -484,6 +485,7 @@ void LSImpl::CheckReset(TopicData* topic) {
|
||||
}
|
||||
topic->lastValue = {};
|
||||
topic->lastValueNetwork = {};
|
||||
topic->lastValueFromNetwork = false;
|
||||
topic->type = NT_UNASSIGNED;
|
||||
topic->typeStr.clear();
|
||||
topic->flags = 0;
|
||||
@@ -499,10 +501,12 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value,
|
||||
if (topic->type != NT_UNASSIGNED && topic->type != value.type()) {
|
||||
return false;
|
||||
}
|
||||
if (!topic->lastValue || value.time() >= topic->lastValue.time()) {
|
||||
if (!topic->lastValue || topic->lastValue.time() == 0 ||
|
||||
value.time() >= topic->lastValue.time()) {
|
||||
// TODO: notify option even if older value
|
||||
topic->type = value.type();
|
||||
topic->lastValue = value;
|
||||
topic->lastValueFromNetwork = false;
|
||||
NotifyValue(topic, eventFlags, isDuplicate, publisher);
|
||||
}
|
||||
if (!isDuplicate && topic->datalogType == value.type()) {
|
||||
@@ -858,6 +862,17 @@ SubscriberData* LSImpl::AddLocalSubscriber(TopicData* topic,
|
||||
DEBUG4("-> NetworkSubscribe({})", topic->name);
|
||||
m_network->Subscribe(subscriber->handle, {{topic->name}}, config);
|
||||
}
|
||||
|
||||
// queue current value
|
||||
if (subscriber->active) {
|
||||
if (!topic->lastValueFromNetwork && !config.disableLocal) {
|
||||
subscriber->pollStorage.emplace_back(topic->lastValue);
|
||||
subscriber->handle.Set();
|
||||
} else if (topic->lastValueFromNetwork && !config.disableRemote) {
|
||||
subscriber->pollStorage.emplace_back(topic->lastValueNetwork);
|
||||
subscriber->handle.Set();
|
||||
}
|
||||
}
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
@@ -895,6 +910,7 @@ std::unique_ptr<EntryData> LSImpl::RemoveEntry(NT_Entry entryHandle) {
|
||||
|
||||
MultiSubscriberData* LSImpl::AddMultiSubscriber(
|
||||
std::span<const std::string_view> prefixes, const PubSubOptions& options) {
|
||||
DEBUG4("AddMultiSubscriber({})", fmt::join(prefixes, ","));
|
||||
auto subscriber = m_multiSubscribers.Add(m_inst, prefixes, options);
|
||||
// subscribe to any already existing topics
|
||||
for (auto&& topic : m_topics) {
|
||||
@@ -906,6 +922,7 @@ MultiSubscriberData* LSImpl::AddMultiSubscriber(
|
||||
}
|
||||
}
|
||||
if (m_network) {
|
||||
DEBUG4("-> NetworkSubscribe");
|
||||
m_network->Subscribe(subscriber->handle, subscriber->prefixes,
|
||||
subscriber->options);
|
||||
}
|
||||
@@ -1213,6 +1230,10 @@ PublisherData* LSImpl::PublishEntry(EntryData* entry, NT_Type type) {
|
||||
// create publisher
|
||||
entry->publisher = AddLocalPublisher(entry->topic, wpi::json::object(),
|
||||
entry->subscriber->config);
|
||||
// exclude publisher if requested
|
||||
if (entry->subscriber->config.excludeSelf) {
|
||||
entry->subscriber->config.excludePublisher = entry->publisher->handle;
|
||||
}
|
||||
return entry->publisher;
|
||||
}
|
||||
|
||||
@@ -1265,9 +1286,6 @@ bool LSImpl::SetEntryValue(NT_Handle pubentryHandle, const Value& value) {
|
||||
if (!publisher) {
|
||||
if (auto entry = m_entries.Get(pubentryHandle)) {
|
||||
publisher = PublishEntry(entry, value.type());
|
||||
if (entry->subscriber->config.excludeSelf) {
|
||||
entry->subscriber->config.excludePublisher = publisher->handle;
|
||||
}
|
||||
}
|
||||
if (!publisher) {
|
||||
return false;
|
||||
@@ -1376,6 +1394,7 @@ void LocalStorage::NetworkSetValue(NT_Topic topicHandle, const Value& value) {
|
||||
if (m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE,
|
||||
value == topic->lastValue, nullptr)) {
|
||||
topic->lastValueNetwork = value;
|
||||
topic->lastValueFromNetwork = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
});
|
||||
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());
|
||||
}
|
||||
});
|
||||
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);
|
||||
@@ -451,13 +495,24 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
});
|
||||
ws.binary.connect([this](std::span<const uint8_t> data, bool) {
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->ProcessIncomingBinary(data);
|
||||
m_clientImpl->ProcessIncomingBinary(m_loop.Now().count(), data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -501,16 +561,15 @@ void NetworkClient::StopDSClient() {
|
||||
}
|
||||
|
||||
void NetworkClient::FlushLocal() {
|
||||
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) { m_impl->HandleLocal(); });
|
||||
if (auto async = m_impl->m_flushLocalAtomic.load(std::memory_order_relaxed)) {
|
||||
async->UnsafeSend();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkClient::Flush() {
|
||||
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) {
|
||||
m_impl->HandleLocal();
|
||||
if (m_impl->m_clientImpl) {
|
||||
m_impl->m_clientImpl->SendValues(m_impl->m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
if (auto async = m_impl->m_flushAtomic.load(std::memory_order_relaxed)) {
|
||||
async->UnsafeSend();
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkClient3::Impl final : public NCImpl3 {
|
||||
@@ -536,6 +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 {
|
||||
|
||||
@@ -53,12 +53,12 @@ class CImpl : public ServerMessageHandler {
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
bool SendControl(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs, bool flush);
|
||||
void SendInitialValues();
|
||||
bool CheckNetworkReady();
|
||||
bool CheckNetworkReady(uint64_t curTimeMs);
|
||||
|
||||
// ServerMessageHandler interface
|
||||
void ServerAnnounce(std::string_view name, int64_t id,
|
||||
@@ -91,6 +91,7 @@ class CImpl : public ServerMessageHandler {
|
||||
// timestamp handling
|
||||
static constexpr uint32_t kPingIntervalMs = 3000;
|
||||
uint64_t m_nextPingTimeMs{0};
|
||||
uint64_t m_pongTimeMs{0};
|
||||
uint32_t m_rtt2Us{UINT32_MAX};
|
||||
bool m_haveTimeOffset{false};
|
||||
int64_t m_serverTimeOffsetUs{0};
|
||||
@@ -98,7 +99,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;
|
||||
@@ -126,7 +126,8 @@ CImpl::CImpl(
|
||||
m_setPeriodic(m_periodMs);
|
||||
}
|
||||
|
||||
void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
void CImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
for (;;) {
|
||||
if (data.empty()) {
|
||||
break;
|
||||
@@ -151,6 +152,7 @@ void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
}
|
||||
DEBUG4("RTT ping response time {} value {}", value.time(),
|
||||
value.GetInteger());
|
||||
m_pongTimeMs = curTimeMs;
|
||||
int64_t now = wpi::Now();
|
||||
int64_t rtt2 = (now - value.GetInteger()) / 2;
|
||||
if (rtt2 < m_rtt2Us) {
|
||||
@@ -208,7 +210,13 @@ 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 we didn't receive a response to our last ping, disconnect
|
||||
if (m_nextPingTimeMs != 0 && m_pongTimeMs == 0) {
|
||||
m_wire.Disconnect("timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return false;
|
||||
}
|
||||
auto now = wpi::Now();
|
||||
@@ -216,10 +224,11 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
WireEncodeBinary(m_wire.SendBinary().Add(), -1, 0, Value::MakeInteger(now));
|
||||
// drift isn't critical here, so just go from current time
|
||||
m_nextPingTimeMs = curTimeMs + kPingIntervalMs;
|
||||
m_pongTimeMs = 0;
|
||||
}
|
||||
|
||||
if (!m_outgoing.empty()) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return false;
|
||||
}
|
||||
auto writer = m_wire.SendText();
|
||||
@@ -237,7 +246,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void CImpl::SendValues(uint64_t curTimeMs) {
|
||||
void CImpl::SendValues(uint64_t curTimeMs, bool flush) {
|
||||
DEBUG4("SendValues({})", curTimeMs);
|
||||
|
||||
// can't send value updates until we have a RTT
|
||||
@@ -254,10 +263,11 @@ void CImpl::SendValues(uint64_t curTimeMs) {
|
||||
bool checkedNetwork = false;
|
||||
auto writer = m_wire.SendBinary();
|
||||
for (auto&& pub : m_publishers) {
|
||||
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
|
||||
if (pub && !pub->outValues.empty() &&
|
||||
(flush || curTimeMs >= pub->nextSendMs)) {
|
||||
for (auto&& val : pub->outValues) {
|
||||
if (!checkedNetwork) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
checkedNetwork = true;
|
||||
@@ -267,6 +277,10 @@ void CImpl::SendValues(uint64_t curTimeMs) {
|
||||
int64_t time = val.time();
|
||||
if (time != 0) {
|
||||
time += m_serverTimeOffsetUs;
|
||||
// make sure resultant time isn't exactly 0
|
||||
if (time == 0) {
|
||||
time = 1;
|
||||
}
|
||||
}
|
||||
WireEncodeBinary(writer.Add(), Handle{pub->handle}.GetIndex(), time,
|
||||
val);
|
||||
@@ -307,15 +321,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;
|
||||
}
|
||||
|
||||
@@ -461,8 +475,9 @@ void ClientImpl::ProcessIncomingText(std::string_view data) {
|
||||
WireDecodeText(data, *m_impl, m_impl->m_logger);
|
||||
}
|
||||
|
||||
void ClientImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(data);
|
||||
void ClientImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(curTimeMs, data);
|
||||
}
|
||||
|
||||
void ClientImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
|
||||
@@ -474,8 +489,8 @@ void ClientImpl::SendControl(uint64_t curTimeMs) {
|
||||
m_impl->m_wire.Flush();
|
||||
}
|
||||
|
||||
void ClientImpl::SendValues(uint64_t curTimeMs) {
|
||||
m_impl->SendValues(curTimeMs);
|
||||
void ClientImpl::SendValues(uint64_t curTimeMs, bool flush) {
|
||||
m_impl->SendValues(curTimeMs, flush);
|
||||
m_impl->m_wire.Flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ class ClientImpl {
|
||||
~ClientImpl();
|
||||
|
||||
void ProcessIncomingText(std::string_view data);
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
|
||||
void SendControl(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs, bool flush);
|
||||
|
||||
void SetLocal(LocalInterface* local);
|
||||
void SendInitial();
|
||||
|
||||
@@ -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,7 @@ class ClientData4 final : public ClientData4Base {
|
||||
|
||||
private:
|
||||
std::vector<ServerMessage> m_outgoing;
|
||||
int m_notReadyCount{0};
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
|
||||
bool WriteBinary(int64_t id, int64_t time, const Value& value) {
|
||||
return WireEncodeBinary(SendBinary().Add(), id, time, value);
|
||||
@@ -247,7 +238,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} {}
|
||||
@@ -292,8 +283,8 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireDecoder3 m_decoder;
|
||||
|
||||
std::vector<net3::Message3> m_outgoing;
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
int64_t m_nextPubUid{1};
|
||||
int m_notReadyCount{0};
|
||||
|
||||
struct TopicData3 {
|
||||
explicit TopicData3(TopicData* topic) { UpdateFlags(topic); }
|
||||
@@ -601,13 +592,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 +643,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 +656,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 +676,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
|
||||
@@ -851,24 +859,23 @@ void ClientData4::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
if (auto m = std::get_if<ServerValueMsg>(&msg.contents)) {
|
||||
if (m->topic == topic->id) {
|
||||
// replace, or append if not present
|
||||
auto [it, added] =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
if (auto m =
|
||||
std::get_if<ServerValueMsg>(&m_outgoing[it->second].contents)) {
|
||||
if (m->topic == topic->id) { // should always be true
|
||||
m->value = value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_outgoing.emplace_back(
|
||||
ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
}
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -937,13 +944,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)) {
|
||||
@@ -953,6 +960,7 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -985,6 +993,7 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
mode = ClientData::kSendImmNoFlush; // always send local immediately
|
||||
}
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
bool added = false;
|
||||
|
||||
switch (mode) {
|
||||
case ClientData::kSendDisabled: // do nothing
|
||||
@@ -1005,24 +1014,26 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
// replace, or append if not present
|
||||
wpi::DenseMap<NT_Topic, size_t>::iterator it;
|
||||
std::tie(it, added) =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
auto& msg = m_outgoing[it->second];
|
||||
if (msg.Is(net3::Message3::kEntryUpdate) ||
|
||||
msg.Is(net3::Message3::kEntryAssign)) {
|
||||
if (msg.id() == topic->id) {
|
||||
if (msg.id() == topic->id) { // should always be true
|
||||
msg.SetValue(value);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
if (!added) {
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
}
|
||||
++topic3->seqNum;
|
||||
if (topic3->sentAssign) {
|
||||
m_outgoing.emplace_back(net3::Message3::EntryUpdate(
|
||||
@@ -1110,19 +1121,20 @@ 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) {
|
||||
net3::WireEncode(out.stream(), msg);
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -1281,7 +1293,7 @@ void ClientData3::EntryAssign(std::string_view name, unsigned int id,
|
||||
auto topic = m_server.CreateTopic(this, name, typeStr, properties);
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
if (topic3->published || topic3->sentAssign) {
|
||||
WARNING("ignorning client {} duplicate publish of '{}'", m_id, name);
|
||||
WARNING("ignoring client {} duplicate publish of '{}'", m_id, name);
|
||||
return;
|
||||
}
|
||||
++topic3->seqNum;
|
||||
@@ -1501,39 +1513,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 +2127,7 @@ void SImpl::SetFlags(ClientData* client, TopicData* topic, unsigned int flags) {
|
||||
void SImpl::SetValue(ClientData* client, TopicData* topic, const Value& value) {
|
||||
// update retained value if from same client or timestamp newer
|
||||
if (!topic->lastValue || topic->lastValueClient == client ||
|
||||
value.time() >= topic->lastValue.time()) {
|
||||
topic->lastValue.time() == 0 || value.time() >= topic->lastValue.time()) {
|
||||
DEBUG4("updating '{}' last value (time was {} is {})", topic->name,
|
||||
topic->lastValue.time(), value.time());
|
||||
topic->lastValue = value;
|
||||
@@ -2287,9 +2289,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 {
|
||||
|
||||
@@ -91,9 +92,9 @@ class CImpl : public MessageHandler3 {
|
||||
|
||||
void ProcessIncoming(std::span<const uint8_t> data);
|
||||
void HandleLocal(std::span<const net::ClientMessage> msgs);
|
||||
void SendPeriodic(uint64_t curTimeMs, bool initial);
|
||||
void SendPeriodic(uint64_t curTimeMs, bool initial, bool flush);
|
||||
void SendValue(Writer& out, Entry* entry, const Value& value);
|
||||
bool CheckNetworkReady();
|
||||
bool CheckNetworkReady(uint64_t curTimeMs);
|
||||
|
||||
// Outgoing handlers
|
||||
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
|
||||
@@ -142,7 +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;
|
||||
@@ -223,7 +223,7 @@ void CImpl::HandleLocal(std::span<const net::ClientMessage> msgs) {
|
||||
}
|
||||
}
|
||||
|
||||
void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
|
||||
void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
|
||||
DEBUG4("SendPeriodic({})", curTimeMs);
|
||||
|
||||
// rate limit sends
|
||||
@@ -233,9 +233,9 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
|
||||
|
||||
auto out = m_wire.Send();
|
||||
|
||||
// send keep-alives
|
||||
// send keep-alive
|
||||
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) {
|
||||
|
||||
// send any stored-up flags updates
|
||||
if (!m_outgoingFlags.empty()) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
for (auto&& p : m_outgoingFlags) {
|
||||
@@ -258,9 +258,10 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
|
||||
// send any pending updates due to be sent
|
||||
bool checkedNetwork = false;
|
||||
for (auto&& pub : m_publishers) {
|
||||
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
|
||||
if (pub && !pub->outValues.empty() &&
|
||||
(flush || curTimeMs >= pub->nextSendMs)) {
|
||||
if (!checkedNetwork) {
|
||||
if (!CheckNetworkReady()) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
}
|
||||
checkedNetwork = true;
|
||||
@@ -301,15 +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;
|
||||
}
|
||||
|
||||
@@ -420,7 +421,7 @@ void CImpl::ServerHelloDone() {
|
||||
}
|
||||
|
||||
// send initial assignments
|
||||
SendPeriodic(m_initTimeMs, true);
|
||||
SendPeriodic(m_initTimeMs, true, true);
|
||||
|
||||
m_state = kStateRunning;
|
||||
m_setPeriodic(m_periodMs);
|
||||
@@ -633,8 +634,8 @@ void ClientImpl3::HandleLocal(std::span<const net::ClientMessage> msgs) {
|
||||
m_impl->HandleLocal(msgs);
|
||||
}
|
||||
|
||||
void ClientImpl3::SendPeriodic(uint64_t curTimeMs) {
|
||||
m_impl->SendPeriodic(curTimeMs, false);
|
||||
void ClientImpl3::SendPeriodic(uint64_t curTimeMs, bool flush) {
|
||||
m_impl->SendPeriodic(curTimeMs, false, flush);
|
||||
}
|
||||
|
||||
void ClientImpl3::SetLocal(net::LocalInterface* local) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class ClientImpl3 {
|
||||
void ProcessIncoming(std::span<const uint8_t> data);
|
||||
void HandleLocal(std::span<const net::ClientMessage> msgs);
|
||||
|
||||
void SendPeriodic(uint64_t curTimeMs);
|
||||
void SendPeriodic(uint64_t curTimeMs, bool flush);
|
||||
|
||||
void SetLocal(net::LocalInterface* local);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user