mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9206b47d67 | ||
|
|
6fc16264ce | ||
|
|
ad18f35477 | ||
|
|
0c6bd846bc | ||
|
|
19c1556472 | ||
|
|
3928ed5647 | ||
|
|
e408f3ad27 | ||
|
|
7957f4a625 | ||
|
|
1241dfdf68 | ||
|
|
4d109309c9 | ||
|
|
7560d18e09 | ||
|
|
f518e143d0 | ||
|
|
6fab87fa4c | ||
|
|
42c41785ac | ||
|
|
1a7eeb6282 | ||
|
|
98c0827236 | ||
|
|
57aa8ca0dd | ||
|
|
789af2ad26 | ||
|
|
9a5366bb83 | ||
|
|
77c09b9ce2 | ||
|
|
9ec27c1202 | ||
|
|
d653408873 | ||
|
|
24a24c9051 | ||
|
|
0e5eb3f35c | ||
|
|
4b15c73f64 | ||
|
|
a274e297cd | ||
|
|
d198605562 | ||
|
|
dfaad7ca22 | ||
|
|
2df82ec957 | ||
|
|
3661f485af | ||
|
|
7f9389f101 | ||
|
|
ca35bcd827 | ||
|
|
9227d09960 | ||
|
|
370126db38 | ||
|
|
1330235918 | ||
|
|
d392570659 | ||
|
|
a2d45dbca4 | ||
|
|
30965b20cf | ||
|
|
5bc942f532 | ||
|
|
97828bd325 | ||
|
|
6da21c4943 | ||
|
|
ecf1755e3e | ||
|
|
154d920e67 | ||
|
|
d2ee423749 | ||
|
|
7e3678b0a4 | ||
|
|
4a55d830e4 | ||
|
|
420020c0d5 | ||
|
|
077c8f4092 | ||
|
|
84e3a22baa | ||
|
|
b482321c0d | ||
|
|
d181e353a0 | ||
|
|
2386e44f3a | ||
|
|
fa5b604f16 | ||
|
|
67e8306819 | ||
|
|
1981b8debd | ||
|
|
ba9c21cf38 | ||
|
|
211c2a375c | ||
|
|
75b2fa1cc3 | ||
|
|
84b089b209 | ||
|
|
ce550705d7 | ||
|
|
3989617bde | ||
|
|
f1836e1321 | ||
|
|
d05f179a9a | ||
|
|
b1b03bed85 | ||
|
|
fa63fbf446 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,4 +1,7 @@
|
||||
*.cpp text
|
||||
*.gradle text eol=lf
|
||||
*.h text
|
||||
*.inc text
|
||||
*.java text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
|
||||
2
.github/workflows/comment-command.yml
vendored
2
.github/workflows/comment-command.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run wpiformat
|
||||
|
||||
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -70,13 +70,13 @@ jobs:
|
||||
artifact-name: Win64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly --max-workers 1"
|
||||
build-options: "-PciDebugOnly"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly --max-workers 1"
|
||||
build-options: "-PciReleaseOnly"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
@@ -84,13 +84,13 @@ jobs:
|
||||
artifact-name: WinArm64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly -Pbuildwinarm64 -Ponlywindowsarm64 --max-workers 1"
|
||||
build-options: "-PciDebugOnly -Pbuildwinarm64 -Ponlywindowsarm64"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
- os: windows-2022
|
||||
artifact-name: WinArm64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly -Pbuildwinarm64 -Ponlywindowsarm64 --max-workers 1"
|
||||
build-options: "-PciReleaseOnly -Pbuildwinarm64 -Ponlywindowsarm64"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
build-dir: "c:\\work"
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
java-version: 17
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
2
.github/workflows/lint-format.yml
vendored
2
.github/workflows/lint-format.yml
vendored
@@ -108,6 +108,6 @@ jobs:
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 13
|
||||
java-version: 17
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2023 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2024 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -1204,3 +1204,8 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================
|
||||
2024 Field Image
|
||||
================
|
||||
2024 Field Image from MikLast: https://www.chiefdelphi.com/t/2024-crescendo-top-down-field-renders/447764
|
||||
|
||||
@@ -27,6 +27,7 @@ import edu.wpi.first.networktables.StringArrayTopic;
|
||||
import edu.wpi.first.networktables.StringEntry;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -140,6 +141,7 @@ public final class CameraServer {
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
Reference.reachabilityFence(m_videoListener);
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
@@ -223,7 +225,7 @@ public final class CameraServer {
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
@FILENAME_DEP_REPLACE@
|
||||
@WPIUTIL_DEP_REPLACE@
|
||||
@WPINET_DEP_REPLACE@
|
||||
find_dependency(OpenCV)
|
||||
|
||||
@FILENAME_DEP_REPLACE@
|
||||
|
||||
@@ -74,18 +74,62 @@ public class CameraServerCvJNI {
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a CV source.
|
||||
*
|
||||
* @param name Name.
|
||||
* @param pixelFormat OpenCV pixel format.
|
||||
* @param width Image width.
|
||||
* @param height Image height.
|
||||
* @param fps Frames per second.
|
||||
* @return CV source.
|
||||
*/
|
||||
public static native int createCvSource(
|
||||
String name, int pixelFormat, int width, int height, int fps);
|
||||
|
||||
/**
|
||||
* Put source frame.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
*/
|
||||
public static native void putSourceFrame(int source, long imageNativeObj);
|
||||
|
||||
/**
|
||||
* Creates a CV sink.
|
||||
*
|
||||
* @param name Name.
|
||||
* @param pixelFormat OpenCV pixel format.
|
||||
* @return CV sink handle.
|
||||
*/
|
||||
public static native int createCvSink(String name, int pixelFormat);
|
||||
|
||||
// /**
|
||||
// * Creates a CV sink callback.
|
||||
// *
|
||||
// * @param name Name.
|
||||
// * @param processFrame Process frame callback.
|
||||
// */
|
||||
// public static native int createCvSinkCallback(String name,
|
||||
// void (*processFrame)(long time));
|
||||
|
||||
/**
|
||||
* Returns sink frame handle.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
* @return Sink frame handle.
|
||||
*/
|
||||
public static native long grabSinkFrame(int sink, long imageNativeObj);
|
||||
|
||||
/**
|
||||
* Returns sink frame timeout in microseconds.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param imageNativeObj Image native object handle.
|
||||
* @param timeout Timeout in seconds.
|
||||
* @return Sink frame timeout in microseconds.
|
||||
*/
|
||||
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
|
||||
|
||||
/** Utility class. */
|
||||
|
||||
@@ -77,140 +77,521 @@ public class CameraServerJNI {
|
||||
//
|
||||
// Property Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns property kind.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property kind.
|
||||
*/
|
||||
public static native int getPropertyKind(int property);
|
||||
|
||||
/**
|
||||
* Returns property name.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property name.
|
||||
*/
|
||||
public static native String getPropertyName(int property);
|
||||
|
||||
/**
|
||||
* Returns property value.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property value.
|
||||
*/
|
||||
public static native int getProperty(int property);
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @param value Property value.
|
||||
*/
|
||||
public static native void setProperty(int property, int value);
|
||||
|
||||
/**
|
||||
* Returns property minimum.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property minimum.
|
||||
*/
|
||||
public static native int getPropertyMin(int property);
|
||||
|
||||
/**
|
||||
* Returns property maximum.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property maximum.
|
||||
*/
|
||||
public static native int getPropertyMax(int property);
|
||||
|
||||
/**
|
||||
* Returns property step.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property step.
|
||||
*/
|
||||
public static native int getPropertyStep(int property);
|
||||
|
||||
/**
|
||||
* Returns property default value.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property default value.
|
||||
*/
|
||||
public static native int getPropertyDefault(int property);
|
||||
|
||||
/**
|
||||
* Returns property value as a string.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Property value as a string.
|
||||
*/
|
||||
public static native String getStringProperty(int property);
|
||||
|
||||
/**
|
||||
* Sets property value to a string.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @param value Property value string.
|
||||
*/
|
||||
public static native void setStringProperty(int property, String value);
|
||||
|
||||
/**
|
||||
* Returns enum of possible property value strings.
|
||||
*
|
||||
* @param property Property handle.
|
||||
* @return Enum of possible property value strings.
|
||||
*/
|
||||
public static native String[] getEnumPropertyChoices(int property);
|
||||
|
||||
//
|
||||
// Source Creation Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Creates a new USB camera by device.
|
||||
*
|
||||
* @param name USB camera name.
|
||||
* @param dev USB camera device number.
|
||||
* @return USB camera handle.
|
||||
*/
|
||||
public static native int createUsbCameraDev(String name, int dev);
|
||||
|
||||
/**
|
||||
* Creates a new USB camera by path.
|
||||
*
|
||||
* @param name USB camera name.
|
||||
* @param path USB camera path.
|
||||
* @return USB camera handle.
|
||||
*/
|
||||
public static native int createUsbCameraPath(String name, String path);
|
||||
|
||||
/**
|
||||
* Creates an HTTP camera.
|
||||
*
|
||||
* @param name HTTP camera name.
|
||||
* @param url HTTP camera stream URL.
|
||||
* @param kind HTTP camera kind.
|
||||
* @return HTTP camera handle.
|
||||
*/
|
||||
public static native int createHttpCamera(String name, String url, int kind);
|
||||
|
||||
/**
|
||||
* Creates an HTTP camera from multiple URLs.
|
||||
*
|
||||
* @param name HTTP camera name.
|
||||
* @param urls HTTP camera stream URLs.
|
||||
* @param kind HTTP camera kind.
|
||||
* @return HTTP camera handle.
|
||||
*/
|
||||
public static native int createHttpCameraMulti(String name, String[] urls, int kind);
|
||||
|
||||
/**
|
||||
* Creates a raw source.
|
||||
*
|
||||
* @param name Source name.
|
||||
* @param pixelFormat Pixel format.
|
||||
* @param width Image width.
|
||||
* @param height Image height.
|
||||
* @param fps Source frames per second.
|
||||
* @return Raw source handle.
|
||||
*/
|
||||
public static native int createRawSource(
|
||||
String name, int pixelFormat, int width, int height, int fps);
|
||||
|
||||
//
|
||||
// Source Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns source kind.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source kind.
|
||||
*/
|
||||
public static native int getSourceKind(int source);
|
||||
|
||||
/**
|
||||
* Returns source name.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source name.
|
||||
*/
|
||||
public static native String getSourceName(int source);
|
||||
|
||||
/**
|
||||
* Returns source description.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source description.
|
||||
*/
|
||||
public static native String getSourceDescription(int source);
|
||||
|
||||
/**
|
||||
* Returns source's last frame time.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source's last frame time.
|
||||
*/
|
||||
public static native long getSourceLastFrameTime(int source);
|
||||
|
||||
/**
|
||||
* Sets source connection strategy.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param strategy Connection strategy.
|
||||
*/
|
||||
public static native void setSourceConnectionStrategy(int source, int strategy);
|
||||
|
||||
/**
|
||||
* Returns true if source is connected.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return True if source is connected.
|
||||
*/
|
||||
public static native boolean isSourceConnected(int source);
|
||||
|
||||
/**
|
||||
* Returns true if source is enabled.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return True if source is enabled.
|
||||
*/
|
||||
public static native boolean isSourceEnabled(int source);
|
||||
|
||||
/**
|
||||
* Returns source property.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param name Source property name.
|
||||
* @return Source property.
|
||||
*/
|
||||
public static native int getSourceProperty(int source, String name);
|
||||
|
||||
/**
|
||||
* Returns list of source property handles.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return List of source property handles.
|
||||
*/
|
||||
public static native int[] enumerateSourceProperties(int source);
|
||||
|
||||
/**
|
||||
* Returns source video mode.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source video mode.
|
||||
*/
|
||||
public static native VideoMode getSourceVideoMode(int source);
|
||||
|
||||
/**
|
||||
* Sets source video mode.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param pixelFormat Pixel format.
|
||||
* @param width Image width.
|
||||
* @param height Image height.
|
||||
* @param fps Source frames per second.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSourceVideoMode(
|
||||
int source, int pixelFormat, int width, int height, int fps);
|
||||
|
||||
/**
|
||||
* Sets source pixel format.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param pixelFormat Source pixel format.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
|
||||
|
||||
/**
|
||||
* Sets source resolution.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param width Image width.
|
||||
* @param height Image height.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSourceResolution(int source, int width, int height);
|
||||
|
||||
/**
|
||||
* Sets source FPS.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param fps Source frames per second.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSourceFPS(int source, int fps);
|
||||
|
||||
/**
|
||||
* Sets source configuration JSON.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param config Configuration JSON.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSourceConfigJson(int source, String config);
|
||||
|
||||
/**
|
||||
* Returns source configuration JSON.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Source configuration JSON.
|
||||
*/
|
||||
public static native String getSourceConfigJson(int source);
|
||||
|
||||
/**
|
||||
* Returns list of source's supported video modes.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return List of source's supported video modes.
|
||||
*/
|
||||
public static native VideoMode[] enumerateSourceVideoModes(int source);
|
||||
|
||||
/**
|
||||
* Returns list of source sinks.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return List of source sinks.
|
||||
*/
|
||||
public static native int[] enumerateSourceSinks(int source);
|
||||
|
||||
/**
|
||||
* Copies source.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return New source handle.
|
||||
*/
|
||||
public static native int copySource(int source);
|
||||
|
||||
/**
|
||||
* Releases source.
|
||||
*
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void releaseSource(int source);
|
||||
|
||||
//
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets camera brightness.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param brightness Brightness.
|
||||
*/
|
||||
public static native void setCameraBrightness(int source, int brightness);
|
||||
|
||||
/**
|
||||
* Returns camera brightness.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return Camera brightness.
|
||||
*/
|
||||
public static native int getCameraBrightness(int source);
|
||||
|
||||
/**
|
||||
* Sets camera white balance to auto.
|
||||
*
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void setCameraWhiteBalanceAuto(int source);
|
||||
|
||||
/**
|
||||
* Sets camera white balance to "hold current".
|
||||
*
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void setCameraWhiteBalanceHoldCurrent(int source);
|
||||
|
||||
/**
|
||||
* Sets camera white balance to the given value.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param value White balance.
|
||||
*/
|
||||
public static native void setCameraWhiteBalanceManual(int source, int value);
|
||||
|
||||
/**
|
||||
* Sets camera exposure to auto.
|
||||
*
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void setCameraExposureAuto(int source);
|
||||
|
||||
/**
|
||||
* Sets camera exposure to "hold current".
|
||||
*
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void setCameraExposureHoldCurrent(int source);
|
||||
|
||||
/**
|
||||
* Sets camera exposure to the given value.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param value Exposure.
|
||||
*/
|
||||
public static native void setCameraExposureManual(int source, int value);
|
||||
|
||||
//
|
||||
// UsbCamera Source Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets USB camera path.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param path USB camera path.
|
||||
*/
|
||||
public static native void setUsbCameraPath(int source, String path);
|
||||
|
||||
/**
|
||||
* Returns USB camera path.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return USB camera path.
|
||||
*/
|
||||
public static native String getUsbCameraPath(int source);
|
||||
|
||||
/**
|
||||
* Returns USB camera info.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return USB camera info.
|
||||
*/
|
||||
public static native UsbCameraInfo getUsbCameraInfo(int source);
|
||||
|
||||
//
|
||||
// HttpCamera Source Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns HTTP camera kind.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return HTTP camera kind.
|
||||
*/
|
||||
public static native int getHttpCameraKind(int source);
|
||||
|
||||
/**
|
||||
* Sets HTTP camera URLs.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param urls HTTP camera URLs.
|
||||
*/
|
||||
public static native void setHttpCameraUrls(int source, String[] urls);
|
||||
|
||||
/**
|
||||
* Returns HTTP camera URLs.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @return HTTP camera URLs.
|
||||
*/
|
||||
public static native String[] getHttpCameraUrls(int source);
|
||||
|
||||
//
|
||||
// Image Source Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Puts raw frame into source.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param frame Frame handle.
|
||||
*/
|
||||
public static native void putRawSourceFrame(int source, long frame);
|
||||
|
||||
/**
|
||||
* Puts raw frame into source.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param data Frame byte buffer.
|
||||
* @param size Frame size.
|
||||
* @param width Frame width.
|
||||
* @param height Frame height.
|
||||
* @param stride Frame stride.
|
||||
* @param pixelFormat Frame pixel format.
|
||||
*/
|
||||
public static native void putRawSourceFrameBB(
|
||||
int source, ByteBuffer data, int size, int width, int height, int stride, int pixelFormat);
|
||||
|
||||
/**
|
||||
* Puts raw frame into source.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param data Frame handle.
|
||||
* @param size Frame size.
|
||||
* @param width Frame width.
|
||||
* @param height Frame height.
|
||||
* @param stride Frame stride.
|
||||
* @param pixelFormat Frame pixel format.
|
||||
*/
|
||||
public static native void putRawSourceFrameData(
|
||||
int source, long data, int size, int width, int height, int stride, int pixelFormat);
|
||||
|
||||
/**
|
||||
* Notify source error.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param msg Error message.
|
||||
*/
|
||||
public static native void notifySourceError(int source, String msg);
|
||||
|
||||
/**
|
||||
* Sets whether source is connected.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param connected True if source is connected.
|
||||
*/
|
||||
public static native void setSourceConnected(int source, boolean connected);
|
||||
|
||||
/**
|
||||
* Sets source description.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param description Source description.
|
||||
*/
|
||||
public static native void setSourceDescription(int source, String description);
|
||||
|
||||
/**
|
||||
* Creates a source property.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param name Property name.
|
||||
* @param kind Property kind.
|
||||
* @param minimum Property minimum.
|
||||
* @param maximum Property maximum.
|
||||
* @param step Property step.
|
||||
* @param defaultValue Property default value.
|
||||
* @param value Property value.
|
||||
* @return Source property handle.
|
||||
*/
|
||||
public static native int createSourceProperty(
|
||||
int source,
|
||||
String name,
|
||||
@@ -221,88 +602,288 @@ public class CameraServerJNI {
|
||||
int defaultValue,
|
||||
int value);
|
||||
|
||||
/**
|
||||
* Sets list of possible property values.
|
||||
*
|
||||
* @param source Source handle.
|
||||
* @param property Property handle.
|
||||
* @param choices List of possible property values.
|
||||
*/
|
||||
public static native void setSourceEnumPropertyChoices(
|
||||
int source, int property, String[] choices);
|
||||
|
||||
//
|
||||
// Sink Creation Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Creates an MJPEG server.
|
||||
*
|
||||
* @param name MJPEG server name.
|
||||
* @param listenAddress IP address at which server should listen.
|
||||
* @param port Port on which server should listen.
|
||||
* @return MJPEG server handle.
|
||||
*/
|
||||
public static native int createMjpegServer(String name, String listenAddress, int port);
|
||||
|
||||
/**
|
||||
* Creates a raw sink.
|
||||
*
|
||||
* @param name Sink name.
|
||||
* @return Raw sink handle.
|
||||
*/
|
||||
public static native int createRawSink(String name);
|
||||
|
||||
//
|
||||
// Sink Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns sink kind.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink kind.
|
||||
*/
|
||||
public static native int getSinkKind(int sink);
|
||||
|
||||
/**
|
||||
* Returns sink name.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink name.
|
||||
*/
|
||||
public static native String getSinkName(int sink);
|
||||
|
||||
/**
|
||||
* Returns sink description.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink description.
|
||||
*/
|
||||
public static native String getSinkDescription(int sink);
|
||||
|
||||
/**
|
||||
* Returns sink property.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param name Property name.
|
||||
* @return Sink property handle.
|
||||
*/
|
||||
public static native int getSinkProperty(int sink, String name);
|
||||
|
||||
/**
|
||||
* Returns list of sink property handles.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return List of sink property handles.
|
||||
*/
|
||||
public static native int[] enumerateSinkProperties(int sink);
|
||||
|
||||
/**
|
||||
* Sets sink configuration JSON.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param config Configuration JSON.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setSinkConfigJson(int sink, String config);
|
||||
|
||||
/**
|
||||
* Returns sink configuration JSON.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink configuration JSON.
|
||||
*/
|
||||
public static native String getSinkConfigJson(int sink);
|
||||
|
||||
/**
|
||||
* Sets sink source.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param source Source handle.
|
||||
*/
|
||||
public static native void setSinkSource(int sink, int source);
|
||||
|
||||
/**
|
||||
* Returns sink source property.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param name Property name.
|
||||
* @return Sink source property handle.
|
||||
*/
|
||||
public static native int getSinkSourceProperty(int sink, String name);
|
||||
|
||||
/**
|
||||
* Returns sink source.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink source handle.
|
||||
*/
|
||||
public static native int getSinkSource(int sink);
|
||||
|
||||
/**
|
||||
* Copies sink.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return New sink handle.
|
||||
*/
|
||||
public static native int copySink(int sink);
|
||||
|
||||
/**
|
||||
* Releases sink.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
*/
|
||||
public static native void releaseSink(int sink);
|
||||
|
||||
//
|
||||
// MjpegServer Sink Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns MJPEG server listen address.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return MJPEG server listen address.
|
||||
*/
|
||||
public static native String getMjpegServerListenAddress(int sink);
|
||||
|
||||
/**
|
||||
* Returns MJPEG server port.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return MJPEG server port.
|
||||
*/
|
||||
public static native int getMjpegServerPort(int sink);
|
||||
|
||||
//
|
||||
// Image Sink Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets sink description.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param description Sink description.
|
||||
*/
|
||||
public static native void setSinkDescription(int sink, String description);
|
||||
|
||||
/**
|
||||
* Returns raw sink frame.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param frame Raw frame.
|
||||
* @param nativeObj Native object.
|
||||
* @return Raw sink frame handle.
|
||||
*/
|
||||
public static native long grabRawSinkFrame(int sink, RawFrame frame, long nativeObj);
|
||||
|
||||
/**
|
||||
* Returns raw sink frame timeout.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param frame Raw frame.
|
||||
* @param nativeObj Native object.
|
||||
* @param timeout Timeout in seconds.
|
||||
* @return Raw sink frame timeout.
|
||||
*/
|
||||
public static native long grabRawSinkFrameTimeout(
|
||||
int sink, RawFrame frame, long nativeObj, double timeout);
|
||||
|
||||
/**
|
||||
* Returns sink error message.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @return Sink error message.
|
||||
*/
|
||||
public static native String getSinkError(int sink);
|
||||
|
||||
/**
|
||||
* Sets sink enable.
|
||||
*
|
||||
* @param sink Sink handle.
|
||||
* @param enabled True if sink should be enabled.
|
||||
*/
|
||||
public static native void setSinkEnabled(int sink, boolean enabled);
|
||||
|
||||
//
|
||||
// Listener Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*
|
||||
* @param listener Video event callback.
|
||||
* @param eventMask Event mask.
|
||||
* @param immediateNotify True to immediately notify on event.
|
||||
* @return Listener handle.
|
||||
*/
|
||||
public static native int addListener(
|
||||
Consumer<VideoEvent> listener, int eventMask, boolean immediateNotify);
|
||||
|
||||
/**
|
||||
* Removes listener.
|
||||
*
|
||||
* @param handle Listener handle.
|
||||
*/
|
||||
public static native void removeListener(int handle);
|
||||
|
||||
/**
|
||||
* Creates listener poller.
|
||||
*
|
||||
* @return Listener poller handle.
|
||||
*/
|
||||
public static native int createListenerPoller();
|
||||
|
||||
/**
|
||||
* Destroys listener poller.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
*/
|
||||
public static native void destroyListenerPoller(int poller);
|
||||
|
||||
/**
|
||||
* Add polled listener.
|
||||
*
|
||||
* @param poller Poller handle.
|
||||
* @param eventMask Event mask.
|
||||
* @param immediateNotify True to immediately notify on event.
|
||||
* @return Polled listener handle.
|
||||
*/
|
||||
public static native int addPolledListener(int poller, int eventMask, boolean immediateNotify);
|
||||
|
||||
/**
|
||||
* Polls listener.
|
||||
*
|
||||
* @param poller Poller handle.
|
||||
* @return List of video events.
|
||||
* @throws InterruptedException if polling was interrupted.
|
||||
*/
|
||||
public static native VideoEvent[] pollListener(int poller) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Polls listener with timeout.
|
||||
*
|
||||
* @param poller Poller handle.
|
||||
* @param timeout Timeout in seconds.
|
||||
* @return List of video events.
|
||||
* @throws InterruptedException if polling was interrupted.
|
||||
*/
|
||||
public static native VideoEvent[] pollListenerTimeout(int poller, double timeout)
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Cancels poll listener.
|
||||
*
|
||||
* @param poller Poller handle.
|
||||
*/
|
||||
public static native void cancelPollListener(int poller);
|
||||
|
||||
//
|
||||
// Telemetry Functions
|
||||
//
|
||||
|
||||
/** Telemetry kind. */
|
||||
public enum TelemetryKind {
|
||||
/** kSourceBytesReceived. */
|
||||
kSourceBytesReceived(1),
|
||||
@@ -315,23 +896,66 @@ public class CameraServerJNI {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns telemetry kind value.
|
||||
*
|
||||
* @return Telemetry kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets telemetry period.
|
||||
*
|
||||
* @param seconds Telemetry period in seconds.
|
||||
*/
|
||||
public static native void setTelemetryPeriod(double seconds);
|
||||
|
||||
/**
|
||||
* Returns telemetry elapsed time.
|
||||
*
|
||||
* @return Telemetry elapsed time.
|
||||
*/
|
||||
public static native double getTelemetryElapsedTime();
|
||||
|
||||
/**
|
||||
* Returns telemetry value.
|
||||
*
|
||||
* @param handle Telemetry handle.
|
||||
* @param kind Telemetry kind.
|
||||
* @return Telemetry value.
|
||||
*/
|
||||
public static native long getTelemetryValue(int handle, int kind);
|
||||
|
||||
/**
|
||||
* Returns telemetry value.
|
||||
*
|
||||
* @param handle Telemetry handle.
|
||||
* @param kind Telemetry kind.
|
||||
* @return Telemetry value.
|
||||
*/
|
||||
public static long getTelemetryValue(int handle, TelemetryKind kind) {
|
||||
return getTelemetryValue(handle, kind.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns telemetry average value.
|
||||
*
|
||||
* @param handle Telemetry handle.
|
||||
* @param kind Telemetry kind.
|
||||
* @return Telemetry average value.
|
||||
*/
|
||||
public static native double getTelemetryAverageValue(int handle, int kind);
|
||||
|
||||
/**
|
||||
* Returns telemetry average value.
|
||||
*
|
||||
* @param handle Telemetry handle.
|
||||
* @param kind Telemetry kind.
|
||||
* @return Telemetry average value.
|
||||
*/
|
||||
public static double getTelemetryAverageValue(int handle, TelemetryKind kind) {
|
||||
return getTelemetryAverageValue(handle, kind.getValue());
|
||||
}
|
||||
@@ -339,30 +963,80 @@ public class CameraServerJNI {
|
||||
//
|
||||
// Logging Functions
|
||||
//
|
||||
|
||||
/** Logger functional interface. */
|
||||
@FunctionalInterface
|
||||
public interface LoggerFunction {
|
||||
/**
|
||||
* Log a string.
|
||||
*
|
||||
* @param level Log level.
|
||||
* @param file Log file.
|
||||
* @param line Line number.
|
||||
* @param msg Log message.
|
||||
*/
|
||||
void apply(int level, String file, int line, String msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets logger.
|
||||
*
|
||||
* @param func Logger function.
|
||||
* @param minLevel Minimum logging level.
|
||||
*/
|
||||
public static native void setLogger(LoggerFunction func, int minLevel);
|
||||
|
||||
//
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns list of USB cameras.
|
||||
*
|
||||
* @return List of USB cameras.
|
||||
*/
|
||||
public static native UsbCameraInfo[] enumerateUsbCameras();
|
||||
|
||||
/**
|
||||
* Returns list of sources.
|
||||
*
|
||||
* @return List of sources.
|
||||
*/
|
||||
public static native int[] enumerateSources();
|
||||
|
||||
/**
|
||||
* Returns list of sinks.
|
||||
*
|
||||
* @return List of sinks.
|
||||
*/
|
||||
public static native int[] enumerateSinks();
|
||||
|
||||
/**
|
||||
* Returns hostname.
|
||||
*
|
||||
* @return Hostname.
|
||||
*/
|
||||
public static native String getHostname();
|
||||
|
||||
/**
|
||||
* Returns list of network interfaces.
|
||||
*
|
||||
* @return List of network interfaces.
|
||||
*/
|
||||
public static native String[] getNetworkInterfaces();
|
||||
|
||||
/** Runs main run loop. */
|
||||
public static native void runMainRunLoop();
|
||||
|
||||
/**
|
||||
* Runs main run loop with timeout.
|
||||
*
|
||||
* @param timeoutSeconds Timeout in seconds.
|
||||
* @return 3 on timeout, 2 on signal, 1 on other.
|
||||
*/
|
||||
public static native int runMainRunLoopTimeout(double timeoutSeconds);
|
||||
|
||||
/** Stops main run loop. */
|
||||
public static native void stopMainRunLoop();
|
||||
|
||||
/** Utility class. */
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.cscore;
|
||||
|
||||
/** A source that represents a MJPEG-over-HTTP (IP) camera. */
|
||||
public class HttpCamera extends VideoCamera {
|
||||
/** HTTP camera kind. */
|
||||
public enum HttpCameraKind {
|
||||
/** Unknown camera kind. */
|
||||
kUnknown(0),
|
||||
@@ -22,6 +23,11 @@ public class HttpCamera extends VideoCamera {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HttpCameraKind value.
|
||||
*
|
||||
* @return HttpCameraKind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** A base class for single image reading sinks. */
|
||||
public abstract class ImageSink extends VideoSink {
|
||||
/**
|
||||
* Constructs an ImageSink.
|
||||
*
|
||||
* @param handle The image sink handle.
|
||||
*/
|
||||
protected ImageSink(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
|
||||
package edu.wpi.first.cscore;
|
||||
|
||||
/** A base class for single image providing sources. */
|
||||
public abstract class ImageSource extends VideoSource {
|
||||
/**
|
||||
* Constructs an ImageSource.
|
||||
*
|
||||
* @param handle The image source handle.
|
||||
*/
|
||||
protected ImageSource(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,32 @@ package edu.wpi.first.cscore;
|
||||
|
||||
/** A source that represents a video camera. */
|
||||
public class VideoCamera extends VideoSource {
|
||||
/** White balance. */
|
||||
public static class WhiteBalance {
|
||||
/** Fixed indoor white balance. */
|
||||
public static final int kFixedIndoor = 3000;
|
||||
|
||||
/** Fixed outdoor white balance 1. */
|
||||
public static final int kFixedOutdoor1 = 4000;
|
||||
|
||||
/** Fixed outdoor white balance 2. */
|
||||
public static final int kFixedOutdoor2 = 5000;
|
||||
|
||||
/** Fixed fluorescent white balance 1. */
|
||||
public static final int kFixedFluorescent1 = 5100;
|
||||
|
||||
/** Fixed fluorescent white balance 2. */
|
||||
public static final int kFixedFlourescent2 = 5200;
|
||||
|
||||
/** Default constructor. */
|
||||
public WhiteBalance() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoCamera.
|
||||
*
|
||||
* @param handle The video camera handle.
|
||||
*/
|
||||
protected VideoCamera(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package edu.wpi.first.cscore;
|
||||
/** Video event. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public class VideoEvent {
|
||||
/** VideoEvent kind. */
|
||||
public enum Kind {
|
||||
/** Unknown video event. */
|
||||
kUnknown(0x0000),
|
||||
@@ -57,6 +58,11 @@ public class VideoEvent {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the kind value.
|
||||
*
|
||||
* @return The kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -139,39 +145,67 @@ public class VideoEvent {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/** The video event kind. */
|
||||
public Kind kind;
|
||||
|
||||
// Valid for kSource* and kSink* respectively
|
||||
/**
|
||||
* The source handle.
|
||||
*
|
||||
* <p>Valid for kSource* and kSink* respectively.
|
||||
*/
|
||||
public int sourceHandle;
|
||||
|
||||
/** The sink handle. */
|
||||
public int sinkHandle;
|
||||
|
||||
// Source/sink/property name
|
||||
/** Source/sink/property name. */
|
||||
public String name;
|
||||
|
||||
// Fields for kSourceVideoModeChanged event
|
||||
// Fields for kSourceVideoModeChanged event.
|
||||
|
||||
/** New source video mode. */
|
||||
public VideoMode mode;
|
||||
|
||||
// Fields for kSourceProperty* events
|
||||
// Fields for kSourceProperty* events.
|
||||
|
||||
/** Source property handle. */
|
||||
public int propertyHandle;
|
||||
|
||||
/** Source property kind. */
|
||||
public VideoProperty.Kind propertyKind;
|
||||
|
||||
/** Event value. */
|
||||
public int value;
|
||||
|
||||
/** Event value as a string. */
|
||||
public String valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
/** Listener that was triggered. */
|
||||
public int listener;
|
||||
|
||||
/**
|
||||
* Returns the source associated with the event (if any).
|
||||
*
|
||||
* @return The source associated with the event (if any).
|
||||
*/
|
||||
public VideoSource getSource() {
|
||||
return new VideoSource(CameraServerJNI.copySource(sourceHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sink associated with the event (if any).
|
||||
*
|
||||
* @return The sink associated with the event (if any).
|
||||
*/
|
||||
public VideoSink getSink() {
|
||||
return new VideoSink(CameraServerJNI.copySink(sinkHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property associated with the event (if any).
|
||||
*
|
||||
* @return The property associated with the event (if any).
|
||||
*/
|
||||
public VideoProperty getProperty() {
|
||||
return new VideoProperty(propertyHandle, propertyKind);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ package edu.wpi.first.cscore;
|
||||
public class VideoException extends RuntimeException {
|
||||
private static final long serialVersionUID = -9155939328084105145L;
|
||||
|
||||
/**
|
||||
* Constructs the exception with the given message.
|
||||
*
|
||||
* @param msg The exception message.
|
||||
*/
|
||||
public VideoException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ public class VideoListener implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the video listener handle is valid.
|
||||
*
|
||||
* @return True if the video listener handle is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public class VideoProperty {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -51,69 +56,152 @@ public class VideoProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property name.
|
||||
*
|
||||
* @return Property name.
|
||||
*/
|
||||
public String getName() {
|
||||
return CameraServerJNI.getPropertyName(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property kind.
|
||||
*
|
||||
* @return Property kind.
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is valid.
|
||||
*
|
||||
* @return True if property is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_kind != Kind.kNone;
|
||||
}
|
||||
|
||||
// Kind checkers
|
||||
/**
|
||||
* Returns true if property is a boolean.
|
||||
*
|
||||
* @return True if property is a boolean.
|
||||
*/
|
||||
public boolean isBoolean() {
|
||||
return m_kind == Kind.kBoolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is an integer.
|
||||
*
|
||||
* @return True if property is an integer.
|
||||
*/
|
||||
public boolean isInteger() {
|
||||
return m_kind == Kind.kInteger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is a string.
|
||||
*
|
||||
* @return True if property is a string.
|
||||
*/
|
||||
public boolean isString() {
|
||||
return m_kind == Kind.kString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if property is an enum.
|
||||
*
|
||||
* @return True if property is an enum.
|
||||
*/
|
||||
public boolean isEnum() {
|
||||
return m_kind == Kind.kEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property value.
|
||||
*
|
||||
* @return Property value.
|
||||
*/
|
||||
public int get() {
|
||||
return CameraServerJNI.getProperty(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
*
|
||||
* @param value Property value.
|
||||
*/
|
||||
public void set(int value) {
|
||||
CameraServerJNI.setProperty(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property minimum value.
|
||||
*
|
||||
* @return Property minimum value.
|
||||
*/
|
||||
public int getMin() {
|
||||
return CameraServerJNI.getPropertyMin(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property maximum value.
|
||||
*
|
||||
* @return Property maximum value.
|
||||
*/
|
||||
public int getMax() {
|
||||
return CameraServerJNI.getPropertyMax(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property step size.
|
||||
*
|
||||
* @return Property step size.
|
||||
*/
|
||||
public int getStep() {
|
||||
return CameraServerJNI.getPropertyStep(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns property default value.
|
||||
*
|
||||
* @return Property default value.
|
||||
*/
|
||||
public int getDefault() {
|
||||
return CameraServerJNI.getPropertyDefault(m_handle);
|
||||
}
|
||||
|
||||
// String-specific functions
|
||||
/**
|
||||
* Returns the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @return The string property value.
|
||||
*/
|
||||
public String getString() {
|
||||
return CameraServerJNI.getStringProperty(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @param value String property value.
|
||||
*/
|
||||
public void setString(String value) {
|
||||
CameraServerJNI.setStringProperty(m_handle, value);
|
||||
}
|
||||
|
||||
// Enum-specific functions
|
||||
/**
|
||||
* Returns the possible values for the enum property value.
|
||||
*
|
||||
* <p>This function is enum-specific.
|
||||
*
|
||||
* @return The possible values for the enum property value.
|
||||
*/
|
||||
public String[] getChoices() {
|
||||
return CameraServerJNI.getEnumPropertyChoices(m_handle);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ public class VideoSink implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -48,6 +53,11 @@ public class VideoSink implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoSink.
|
||||
*
|
||||
* @param handle The video sink handle.
|
||||
*/
|
||||
protected VideoSink(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
@@ -60,10 +70,20 @@ public class VideoSink implements AutoCloseable {
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSink is valid.
|
||||
*
|
||||
* @return True if the VideoSink is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video sink handle.
|
||||
*
|
||||
* @return The video sink handle.
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
@@ -222,5 +242,6 @@ public class VideoSink implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** The VideoSink handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import edu.wpi.first.util.PixelFormat;
|
||||
* (e.g. from a stereo or depth camera); these are called channels.
|
||||
*/
|
||||
public class VideoSource implements AutoCloseable {
|
||||
/** Video source kind. */
|
||||
public enum Kind {
|
||||
/** Unknown video source. */
|
||||
kUnknown(0),
|
||||
@@ -29,6 +30,11 @@ public class VideoSource implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Kind value.
|
||||
*
|
||||
* @return The Kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -56,6 +62,11 @@ public class VideoSource implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ConnectionStrategy value.
|
||||
*
|
||||
* @return The ConnectionStrategy value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
@@ -80,6 +91,11 @@ public class VideoSource implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a VideoSource.
|
||||
*
|
||||
* @param handle The video source handle.
|
||||
*/
|
||||
protected VideoSource(int handle) {
|
||||
m_handle = handle;
|
||||
}
|
||||
@@ -92,10 +108,20 @@ public class VideoSource implements AutoCloseable {
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSource is valid.
|
||||
*
|
||||
* @return True if the VideoSource is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return m_handle != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video source handle.
|
||||
*
|
||||
* @return The video source handle.
|
||||
*/
|
||||
public int getHandle() {
|
||||
return m_handle;
|
||||
}
|
||||
@@ -379,5 +405,6 @@ public class VideoSource implements AutoCloseable {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** Video source handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -58,33 +58,139 @@ class VideoProperty {
|
||||
|
||||
VideoProperty() = default;
|
||||
|
||||
/**
|
||||
* Returns property name.
|
||||
*
|
||||
* @return Property name.
|
||||
*/
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
* Returns property kind.
|
||||
*
|
||||
* @return Property kind.
|
||||
*/
|
||||
Kind GetKind() const { return m_kind; }
|
||||
|
||||
/**
|
||||
* Returns true if property is valid.
|
||||
*
|
||||
* @return True if property is valid.
|
||||
*/
|
||||
explicit operator bool() const { return m_kind != kNone; }
|
||||
|
||||
// Kind checkers
|
||||
/**
|
||||
* Returns true if property is a boolean.
|
||||
*
|
||||
* @return True if property is a boolean.
|
||||
*/
|
||||
bool IsBoolean() const { return m_kind == kBoolean; }
|
||||
|
||||
/**
|
||||
* Returns true if property is an integer.
|
||||
*
|
||||
* @return True if property is an integer.
|
||||
*/
|
||||
bool IsInteger() const { return m_kind == kInteger; }
|
||||
|
||||
/**
|
||||
* Returns true if property is a string.
|
||||
*
|
||||
* @return True if property is a string.
|
||||
*/
|
||||
bool IsString() const { return m_kind == kString; }
|
||||
|
||||
/**
|
||||
* Returns true if property is an enum.
|
||||
*
|
||||
* @return True if property is an enum.
|
||||
*/
|
||||
bool IsEnum() const { return m_kind == kEnum; }
|
||||
|
||||
/**
|
||||
* Returns property value.
|
||||
*
|
||||
* @return Property value.
|
||||
*/
|
||||
int Get() const;
|
||||
|
||||
/**
|
||||
* Sets property value.
|
||||
*
|
||||
* @param value Property value.
|
||||
*/
|
||||
void Set(int value);
|
||||
|
||||
/**
|
||||
* Returns property minimum value.
|
||||
*
|
||||
* @return Property minimum value.
|
||||
*/
|
||||
int GetMin() const;
|
||||
|
||||
/**
|
||||
* Returns property maximum value.
|
||||
*
|
||||
* @return Property maximum value.
|
||||
*/
|
||||
int GetMax() const;
|
||||
|
||||
/**
|
||||
* Returns property step size.
|
||||
*
|
||||
* @return Property step size.
|
||||
*/
|
||||
int GetStep() const;
|
||||
|
||||
/**
|
||||
* Returns property default value.
|
||||
*
|
||||
* @return Property default value.
|
||||
*/
|
||||
int GetDefault() const;
|
||||
|
||||
// String-specific functions
|
||||
/**
|
||||
* Returns the string property value.
|
||||
*
|
||||
* <p>This function is string-specific.
|
||||
*
|
||||
* @return The string property value.
|
||||
*/
|
||||
std::string GetString() const;
|
||||
|
||||
/**
|
||||
* Returns the string property value as a reference to the given buffer.
|
||||
*
|
||||
* This function is string-specific.
|
||||
*
|
||||
* @param buf The backing storage to which to write the property value.
|
||||
* @return The string property value as a reference to the given buffer.
|
||||
*/
|
||||
std::string_view GetString(wpi::SmallVectorImpl<char>& buf) const;
|
||||
|
||||
/**
|
||||
* Sets the string property value.
|
||||
*
|
||||
* This function is string-specific.
|
||||
*
|
||||
* @param value String property value.
|
||||
*/
|
||||
void SetString(std::string_view value);
|
||||
|
||||
// Enum-specific functions
|
||||
/**
|
||||
* Returns the possible values for the enum property value.
|
||||
*
|
||||
* This function is enum-specific.
|
||||
*
|
||||
* @return The possible values for the enum property value.
|
||||
*/
|
||||
std::vector<std::string> GetChoices() const;
|
||||
|
||||
/**
|
||||
* Returns the last status.
|
||||
*
|
||||
* @return The last status.
|
||||
*/
|
||||
CS_Status GetLastStatus() const { return m_status; }
|
||||
|
||||
private:
|
||||
@@ -104,6 +210,9 @@ class VideoSource {
|
||||
friend class VideoSink;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Video source kind.
|
||||
*/
|
||||
enum Kind {
|
||||
/// Unknown video source.
|
||||
kUnknown = CS_SOURCE_UNKNOWN,
|
||||
@@ -359,6 +468,8 @@ class VideoSource {
|
||||
explicit VideoSource(CS_Source handle) : m_handle(handle) {}
|
||||
|
||||
mutable CS_Status m_status = 0;
|
||||
|
||||
/// Video source handle.
|
||||
CS_Source m_handle{0};
|
||||
};
|
||||
|
||||
@@ -367,11 +478,19 @@ class VideoSource {
|
||||
*/
|
||||
class VideoCamera : public VideoSource {
|
||||
public:
|
||||
/**
|
||||
* White balance.
|
||||
*/
|
||||
enum WhiteBalance {
|
||||
/// Fixed indoor white balance.
|
||||
kFixedIndoor = 3000,
|
||||
/// Fixed outdoor white balance 1.
|
||||
kFixedOutdoor1 = 4000,
|
||||
/// Fixed outdoor white balance 2.
|
||||
kFixedOutdoor2 = 5000,
|
||||
/// Fixed fluorescent white balance 1.
|
||||
kFixedFluorescent1 = 5100,
|
||||
/// Fixed fluorescent white balance 2.
|
||||
kFixedFlourescent2 = 5200
|
||||
};
|
||||
|
||||
@@ -479,6 +598,9 @@ class UsbCamera : public VideoCamera {
|
||||
*/
|
||||
class HttpCamera : public VideoCamera {
|
||||
public:
|
||||
/**
|
||||
* HTTP camera kind.
|
||||
*/
|
||||
enum HttpCameraKind {
|
||||
/// Unknown camera kind.
|
||||
kUnknown = CS_HTTP_UNKNOWN,
|
||||
@@ -743,8 +865,18 @@ class VideoSink {
|
||||
VideoSink& operator=(VideoSink other) noexcept;
|
||||
~VideoSink();
|
||||
|
||||
/**
|
||||
* Returns true if the VideoSink is valid.
|
||||
*
|
||||
* @return True if the VideoSink is valid.
|
||||
*/
|
||||
explicit operator bool() const { return m_handle != 0; }
|
||||
|
||||
/**
|
||||
* Returns the VideoSink handle.
|
||||
*
|
||||
* @return The VideoSink handle.
|
||||
*/
|
||||
int GetHandle() const { return m_handle; }
|
||||
|
||||
bool operator==(const VideoSink& other) const {
|
||||
@@ -988,17 +1120,23 @@ class ImageSink : public VideoSink {
|
||||
class VideoEvent : public RawEvent {
|
||||
public:
|
||||
/**
|
||||
* Get the source associated with the event (if any).
|
||||
* Returns the source associated with the event (if any).
|
||||
*
|
||||
* @return The source associated with the event (if any).
|
||||
*/
|
||||
VideoSource GetSource() const;
|
||||
|
||||
/**
|
||||
* Get the sink associated with the event (if any).
|
||||
* Returns the sink associated with the event (if any).
|
||||
*
|
||||
* @return The sink associated with the event (if any).
|
||||
*/
|
||||
VideoSink GetSink() const;
|
||||
|
||||
/**
|
||||
* Get the property associated with the event (if any).
|
||||
* Returns the property associated with the event (if any).
|
||||
*
|
||||
* @return The property associated with the event (if any).
|
||||
*/
|
||||
VideoProperty GetProperty() const;
|
||||
};
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,13 @@ task generateJavaDocs(type: Javadoc) {
|
||||
options.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
|
||||
options.links("https://docs.opencv.org/4.x/javadoc/")
|
||||
options.addStringOption("tag", "pre:a:Pre-Condition")
|
||||
options.addBooleanOption("Xdoclint/package:-edu.wpi.first.math.proto," +
|
||||
options.addBooleanOption("Xdoclint/package:" +
|
||||
// TODO: v Document these, then remove them from the list
|
||||
"-edu.wpi.first.hal," +
|
||||
"-edu.wpi.first.hal.can," +
|
||||
"-edu.wpi.first.hal.simulation," +
|
||||
// TODO: ^ Document these, then remove them from the list
|
||||
"-edu.wpi.first.math.proto," +
|
||||
"-edu.wpi.first.math.controller.proto," +
|
||||
"-edu.wpi.first.math.controller.struct," +
|
||||
"-edu.wpi.first.math.geometry.proto," +
|
||||
|
||||
@@ -40,12 +40,10 @@ public class FieldConfig {
|
||||
|
||||
public FieldConfig() {}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public URL getImageUrl() {
|
||||
return getClass().getResource(Fields.kBaseResourceDir + m_fieldImage);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public InputStream getImageAsStream() {
|
||||
return getClass().getResourceAsStream(Fields.kBaseResourceDir + m_fieldImage);
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
"field-image": "2024-field.png",
|
||||
"field-corners": {
|
||||
"top-left": [
|
||||
46,
|
||||
36
|
||||
150,
|
||||
79
|
||||
],
|
||||
"bottom-right": [
|
||||
1088,
|
||||
544
|
||||
2961,
|
||||
1476
|
||||
]
|
||||
},
|
||||
"field-size": [
|
||||
54.27083,
|
||||
26.2916
|
||||
26.9375
|
||||
],
|
||||
"field-unit": "foot"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 2.2 MiB |
@@ -96,7 +96,7 @@ static void NtInitialize() {
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
|
||||
nt::AddPolledLogger(poller, 0, 100);
|
||||
nt::AddPolledLogger(poller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([inst, poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
|
||||
@@ -14,7 +14,7 @@ using namespace glass;
|
||||
static const char* stations[] = {"Invalid", "Red 1", "Red 2", "Red 3",
|
||||
"Blue 1", "Blue 2", "Blue 3"};
|
||||
|
||||
void glass::DisplayFMS(FMSModel* model) {
|
||||
void glass::DisplayFMS(FMSModel* model, bool editableDsAttached) {
|
||||
if (!model->Exists() || model->IsReadOnly()) {
|
||||
return DisplayFMSReadOnly(model);
|
||||
}
|
||||
@@ -31,10 +31,17 @@ void glass::DisplayFMS(FMSModel* model) {
|
||||
// DS Attached
|
||||
if (auto data = model->GetDsAttachedData()) {
|
||||
bool val = data->GetValue();
|
||||
if (ImGui::Checkbox("DS Attached", &val)) {
|
||||
model->SetDsAttached(val);
|
||||
if (editableDsAttached) {
|
||||
if (ImGui::Checkbox("DS Attached", &val)) {
|
||||
model->SetDsAttached(val);
|
||||
}
|
||||
data->EmitDrag();
|
||||
} else {
|
||||
ImGui::Selectable("DS Attached: ");
|
||||
data->EmitDrag();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(val ? "Yes" : "No");
|
||||
}
|
||||
data->EmitDrag();
|
||||
}
|
||||
|
||||
// Alliance Station
|
||||
|
||||
@@ -343,7 +343,7 @@ static bool InputPose(frc::Pose2d* pose) {
|
||||
}
|
||||
|
||||
FieldInfo::FieldInfo(Storage& storage)
|
||||
: m_builtin{storage.GetString("builtin")},
|
||||
: m_builtin{storage.GetString("builtin", "2024 Crescendo")},
|
||||
m_filename{storage.GetString("image")},
|
||||
m_width{storage.GetFloat("width", kDefaultWidth.to<float>())},
|
||||
m_height{storage.GetFloat("height", kDefaultHeight.to<float>())},
|
||||
@@ -508,6 +508,16 @@ bool FieldInfo::LoadJson(std::span<const char> is, std::string_view filename) {
|
||||
height = units::convert<units::feet, units::meters>(height);
|
||||
}
|
||||
|
||||
// check scaling
|
||||
int fieldWidth = m_right - m_left;
|
||||
int fieldHeight = m_bottom - m_top;
|
||||
if (std::abs((fieldWidth / width) - (fieldHeight / height)) > 0.3) {
|
||||
fmt::print(stderr,
|
||||
"GUI: Field X and Y scaling substantially different: "
|
||||
"xscale={} yscale={}\n",
|
||||
(fieldWidth / width), (fieldHeight / height));
|
||||
}
|
||||
|
||||
if (!filename.empty()) {
|
||||
// the image filename is relative to the json file
|
||||
auto pathname = fs::path{filename}.replace_filename(image).string();
|
||||
@@ -560,23 +570,29 @@ FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
|
||||
// fit the image into the window
|
||||
if (m_texture && m_imageHeight != 0 && m_imageWidth != 0) {
|
||||
gui::MaxFit(&min, &max, m_imageWidth, m_imageHeight);
|
||||
} else {
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
}
|
||||
|
||||
FieldFrameData ffd;
|
||||
ffd.imageMin = min;
|
||||
ffd.imageMax = max;
|
||||
|
||||
// size down the box by the image corners (if any)
|
||||
if (m_bottom > 0 && m_right > 0) {
|
||||
min.x += m_left * (max.x - min.x) / m_imageWidth;
|
||||
min.y += m_top * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
|
||||
if (m_bottom > 0 && m_right > 0 && m_imageWidth != 0) {
|
||||
// size down the box by the image corners
|
||||
float scale = (max.x - min.x) / m_imageWidth;
|
||||
min.x += m_left * scale;
|
||||
min.y += m_top * scale;
|
||||
max.x -= (m_imageWidth - m_right) * scale;
|
||||
max.y -= (m_imageHeight - m_bottom) * scale;
|
||||
} else if ((max.x - min.x) > 40 && (max.y - min.y > 40)) {
|
||||
// ensure there's some padding
|
||||
min.x += 20;
|
||||
max.x -= 20;
|
||||
min.y += 20;
|
||||
max.y -= 20;
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
|
||||
ffd.min = min;
|
||||
ffd.max = max;
|
||||
ffd.scale = (max.x - min.x) / m_width;
|
||||
|
||||
@@ -46,8 +46,9 @@ class FMSModel : public Model {
|
||||
*
|
||||
* @param matchTimeEnabled If not null, a checkbox is displayed for
|
||||
* "enable match time" linked to this value
|
||||
* @param editableDsAttached If true, DS attached should be editable
|
||||
*/
|
||||
void DisplayFMS(FMSModel* model);
|
||||
void DisplayFMS(FMSModel* model, bool editableDsAttached);
|
||||
void DisplayFMSReadOnly(FMSModel* model);
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -63,7 +63,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
[](Window* win, Model* model, const char*) {
|
||||
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
return MakeFunctionView(
|
||||
[=] { DisplayFMS(static_cast<FMSModel*>(model)); });
|
||||
[=] { DisplayFMS(static_cast<FMSModel*>(model), true); });
|
||||
});
|
||||
provider.Register(
|
||||
NTDigitalInputModel::kType,
|
||||
|
||||
@@ -20,7 +20,6 @@ generatedFileExclude {
|
||||
modifiableFileExclude {
|
||||
\.patch$
|
||||
\.png$
|
||||
\.py$
|
||||
\.so$
|
||||
}
|
||||
|
||||
|
||||
@@ -10,40 +10,56 @@ def main():
|
||||
# Gets the folder this script is in (the hal/ directory)
|
||||
HAL_ROOT = pathlib.Path(__file__).parent
|
||||
java_package = "edu/wpi/first/hal"
|
||||
(HAL_ROOT/"src/generated/main/native/include/hal").mkdir(parents=True, exist_ok=True)
|
||||
(HAL_ROOT/f"src/generated/main/java/{java_package}").mkdir(parents=True, exist_ok=True)
|
||||
usage_reporting_types_cpp = []
|
||||
# fmt: off
|
||||
(HAL_ROOT / "src/generated/main/native/include/hal").mkdir(parents=True, exist_ok=True)
|
||||
(HAL_ROOT / f"src/generated/main/java/{java_package}").mkdir(parents=True, exist_ok=True)
|
||||
# fmt: on
|
||||
usage_reporting_types_cpp = []
|
||||
usage_reporting_instances_cpp = []
|
||||
usage_reporting_types = []
|
||||
usage_reporting_instances = []
|
||||
with open(HAL_ROOT/"src/generate/Instances.txt") as instances:
|
||||
with open(HAL_ROOT / "src/generate/Instances.txt") as instances:
|
||||
for instance in instances:
|
||||
usage_reporting_instances_cpp.append(f" {instance.strip()},")
|
||||
usage_reporting_instances.append(
|
||||
f" /** {instance.strip()}. */\n"
|
||||
f" public static final int {instance.strip()};")
|
||||
f" public static final int {instance.strip()};"
|
||||
)
|
||||
|
||||
with open(HAL_ROOT/"src/generate/ResourceType.txt") as resource_types:
|
||||
with open(HAL_ROOT / "src/generate/ResourceType.txt") as resource_types:
|
||||
for resource_type in resource_types:
|
||||
usage_reporting_types_cpp.append(f" {resource_type.strip()},")
|
||||
usage_reporting_types.append(
|
||||
f" /** {resource_type.strip()}. */\n"
|
||||
f" public static final int {resource_type.strip()};")
|
||||
f" public static final int {resource_type.strip()};"
|
||||
)
|
||||
|
||||
with open(HAL_ROOT/"src/generate/FRCNetComm.java.in") as java_usage_reporting:
|
||||
contents = (java_usage_reporting.read()
|
||||
with open(HAL_ROOT / "src/generate/FRCNetComm.java.in") as java_usage_reporting:
|
||||
contents = (
|
||||
# fmt: off
|
||||
java_usage_reporting.read()
|
||||
.replace(r"${usage_reporting_types}", "\n".join(usage_reporting_types))
|
||||
.replace(r"${usage_reporting_instances}", "\n".join(usage_reporting_instances)))
|
||||
|
||||
with open(HAL_ROOT/f"src/generated/main/java/{java_package}/FRCNetComm.java", "w") as java_out:
|
||||
.replace(r"${usage_reporting_instances}", "\n".join(usage_reporting_instances))
|
||||
# fmt: on
|
||||
)
|
||||
|
||||
with open(
|
||||
HAL_ROOT / f"src/generated/main/java/{java_package}/FRCNetComm.java", "w"
|
||||
) as java_out:
|
||||
java_out.write(contents)
|
||||
|
||||
with open(HAL_ROOT/"src/generate/FRCUsageReporting.h.in") as cpp_usage_reporting:
|
||||
contents = (cpp_usage_reporting.read()
|
||||
with open(HAL_ROOT / "src/generate/FRCUsageReporting.h.in") as cpp_usage_reporting:
|
||||
contents = (
|
||||
# fmt: off
|
||||
cpp_usage_reporting.read()
|
||||
.replace(r"${usage_reporting_types_cpp}", "\n".join(usage_reporting_types_cpp))
|
||||
.replace(r"${usage_reporting_instances_cpp}", "\n".join(usage_reporting_instances_cpp)))
|
||||
.replace(r"${usage_reporting_instances_cpp}", "\n".join(usage_reporting_instances_cpp))
|
||||
# fmt: on
|
||||
)
|
||||
|
||||
with open(HAL_ROOT/"src/generated/main/native/include/hal/FRCUsageReporting.h", "w") as cpp_out:
|
||||
with open(
|
||||
HAL_ROOT / "src/generated/main/native/include/hal/FRCUsageReporting.h", "w"
|
||||
) as cpp_out:
|
||||
cpp_out.write(contents)
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ package edu.wpi.first.hal.can;
|
||||
import edu.wpi.first.hal.communication.NIRioStatus;
|
||||
import edu.wpi.first.hal.util.UncleanStatusException;
|
||||
|
||||
/**
|
||||
* Checks the status of a CAN message and throws an exception of the appropriate type if necessary.
|
||||
*/
|
||||
public final class CANExceptionFactory {
|
||||
// FRC Error codes
|
||||
static final int ERR_CANSessionMux_InvalidBuffer = -44086;
|
||||
|
||||
@@ -16,11 +16,16 @@ import java.nio.IntBuffer;
|
||||
*/
|
||||
@SuppressWarnings("MethodName")
|
||||
public class CANJNI extends JNIWrapper {
|
||||
/** Flag for sending a CAN message once. */
|
||||
public static final int CAN_SEND_PERIOD_NO_REPEAT = 0;
|
||||
|
||||
/** Flag for stopping periodic CAN message sends. */
|
||||
public static final int CAN_SEND_PERIOD_STOP_REPEATING = -1;
|
||||
|
||||
/* Flags in the upper bits of the messageID */
|
||||
/** Mask for "is frame remote" in message ID. */
|
||||
public static final int CAN_IS_FRAME_REMOTE = 0x80000000;
|
||||
|
||||
/** Mask for "is frame 11 bits" in message ID. */
|
||||
public static final int CAN_IS_FRAME_11BIT = 0x40000000;
|
||||
|
||||
/** Default constructor. */
|
||||
|
||||
@@ -4,13 +4,24 @@
|
||||
|
||||
package edu.wpi.first.hal.communication;
|
||||
|
||||
/** NI RIO status. */
|
||||
public class NIRioStatus {
|
||||
/** RIO status offset. */
|
||||
public static final int kRioStatusOffset = -63000;
|
||||
|
||||
/** Success. */
|
||||
public static final int kRioStatusSuccess = 0;
|
||||
|
||||
/** Buffer invalid size. */
|
||||
public static final int kRIOStatusBufferInvalidSize = kRioStatusOffset - 80;
|
||||
|
||||
/** Operation timed out. */
|
||||
public static final int kRIOStatusOperationTimedOut = -52007;
|
||||
|
||||
/** Feature not supported. */
|
||||
public static final int kRIOStatusFeatureNotSupported = kRioStatusOffset - 193;
|
||||
|
||||
/** Resource not initialized. */
|
||||
public static final int kRIOStatusResourceNotInitialized = -52010;
|
||||
|
||||
/** Default constructor. */
|
||||
|
||||
@@ -198,6 +198,10 @@ void HAL_WriteAddressableLEDData(HAL_AddressableLEDHandle handle,
|
||||
return;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(led->ledBuffer, data, length * sizeof(HAL_AddressableLEDData));
|
||||
|
||||
asm("dmb");
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "AnalogInternal.h"
|
||||
#include "ConstantsInternal.h"
|
||||
#include "DigitalInternal.h"
|
||||
@@ -676,7 +678,7 @@ void HAL_StartDMA(HAL_DMAHandle handle, int32_t queueDepth, int32_t* status) {
|
||||
SET_SIZE(Enable_DutyCycle_Low);
|
||||
SET_SIZE(Enable_DutyCycle_High);
|
||||
#undef SET_SIZE
|
||||
dma->captureStore.captureSize = accum_size + 1;
|
||||
dma->captureStore.captureSize = accum_size + 2;
|
||||
}
|
||||
|
||||
uint32_t byteDepth = queueDepth * dma->captureStore.captureSize;
|
||||
@@ -734,12 +736,22 @@ enum HAL_DMAReadStatus HAL_ReadDMADirect(void* dmaPointer,
|
||||
static_cast<uint32_t>(timeoutSeconds * 1000),
|
||||
&remainingBytes, status);
|
||||
|
||||
if ((remainingBytes % dma->captureStore.captureSize) != 0) {
|
||||
fmt::print(
|
||||
"Remaining bytes {} is not a multiple of capture size {}. This is "
|
||||
"likely a "
|
||||
"bug in WPILib. Please report this issue with a copy of your code.\n",
|
||||
remainingBytes, dma->captureStore.captureSize);
|
||||
}
|
||||
|
||||
*remainingOut = remainingBytes / dma->captureStore.captureSize;
|
||||
|
||||
if (*status == 0) {
|
||||
uint32_t lower_sample =
|
||||
uint64_t upper_sample =
|
||||
dmaSample->readBuffer[dma->captureStore.captureSize - 1];
|
||||
dmaSample->timeStamp = HAL_ExpandFPGATime(lower_sample, status);
|
||||
uint64_t lower_sample =
|
||||
dmaSample->readBuffer[dma->captureStore.captureSize - 2];
|
||||
dmaSample->timeStamp = (upper_sample << 32) + lower_sample;
|
||||
if (*status != 0) {
|
||||
return HAL_DMA_ERROR;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,24 @@
|
||||
|
||||
// These are copies of defines located in CANSessionMux.h prepended with HAL_
|
||||
|
||||
/**
|
||||
* Flag for sending a CAN message once.
|
||||
*/
|
||||
#define HAL_CAN_SEND_PERIOD_NO_REPEAT 0
|
||||
|
||||
/**
|
||||
* Flag for stopping periodic CAN message sends.
|
||||
*/
|
||||
#define HAL_CAN_SEND_PERIOD_STOP_REPEATING -1
|
||||
|
||||
/* Flags in the upper bits of the messageID */
|
||||
/**
|
||||
* Mask for "is frame remote" in message ID.
|
||||
*/
|
||||
#define HAL_CAN_IS_FRAME_REMOTE 0x80000000
|
||||
|
||||
/**
|
||||
* Mask for "is frame 11 bits" in message ID.
|
||||
*/
|
||||
#define HAL_CAN_IS_FRAME_11BIT 0x40000000
|
||||
|
||||
#define HAL_ERR_CANSessionMux_InvalidBuffer -44086
|
||||
|
||||
@@ -382,6 +382,7 @@ void NewDriverStationData() {
|
||||
}
|
||||
lastGiven = given;
|
||||
|
||||
SimDriverStationData->dsAttached = true;
|
||||
driverStation->newDataEvents.Wakeup();
|
||||
SimDriverStationData->CallNewDataCallbacks();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ void DriverStationData::ResetData() {
|
||||
test.Reset(false);
|
||||
eStop.Reset(false);
|
||||
fmsAttached.Reset(false);
|
||||
dsAttached.Reset(true);
|
||||
dsAttached.Reset(false);
|
||||
allianceStationId.Reset(static_cast<HAL_AllianceStationID>(0));
|
||||
matchTime.Reset(-1.0);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class DriverStationData {
|
||||
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetEStopName> eStop{false};
|
||||
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetFmsAttachedName> fmsAttached{
|
||||
false};
|
||||
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetDsAttachedName> dsAttached{true};
|
||||
SimDataValue<HAL_Bool, HAL_MakeBoolean, GetDsAttachedName> dsAttached{false};
|
||||
SimDataValue<HAL_AllianceStationID, MakeAllianceStationIdValue,
|
||||
GetAllianceStationIdName>
|
||||
allianceStationId{static_cast<HAL_AllianceStationID>(0)};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
= Network Tables Alloy Model
|
||||
|
||||
Alloy (http://alloy.mit.edu/alloy/) is a formal logic tool that can analyze
|
||||
[Alloy](https://www.csail.mit.edu/research/alloy) is a formal logic tool that can analyze
|
||||
first-order logic expressions. Under the proposed sequence number -based
|
||||
protocol, assuming that all nodes start from the same state, Alloy is unable to
|
||||
find a way where two nodes with the same sequence number have different state
|
||||
|
||||
@@ -236,6 +236,12 @@ public interface GenericPublisher extends Publisher, Consumer<NetworkTableValue>
|
||||
boolean setDefault{{ t.TypeName }}({{ t.java.ValueType }} defaultValue);
|
||||
{% endif -%}
|
||||
{% if t.java.WrapValueType %}
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
boolean setDefault{{ t.TypeName }}({{ t.java.WrapValueType }} defaultValue);
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
|
||||
@@ -608,6 +608,6 @@ public final class NetworkTableEntry implements Publisher, Subscriber {
|
||||
}
|
||||
|
||||
private final Topic m_topic;
|
||||
protected int m_handle;
|
||||
private final int m_handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,11 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network mode value.
|
||||
*
|
||||
* @return The network mode value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.EnumSet;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** NetworkTables JNI. */
|
||||
public final class NetworkTablesJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
static RuntimeLoader<NetworkTablesJNI> loader = null;
|
||||
@@ -82,139 +83,494 @@ public final class NetworkTablesJNI {
|
||||
return new PubSubOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns default instance handle.
|
||||
*
|
||||
* @return Default instance handle.
|
||||
*/
|
||||
public static native int getDefaultInstance();
|
||||
|
||||
/**
|
||||
* Creates an NT instance.
|
||||
*
|
||||
* @return NT instance handle.
|
||||
*/
|
||||
public static native int createInstance();
|
||||
|
||||
/**
|
||||
* Destroys an NT instance.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void destroyInstance(int inst);
|
||||
|
||||
/**
|
||||
* Returns NT instance from handle.
|
||||
*
|
||||
* @param handle NT instance handle.
|
||||
* @return NT instance.
|
||||
*/
|
||||
public static native int getInstanceFromHandle(int handle);
|
||||
|
||||
private static native int getEntryImpl(
|
||||
int topic, int type, String typeStr, PubSubOptions options);
|
||||
|
||||
/**
|
||||
* Returns NT entry handle.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param key NT entry key.
|
||||
* @return NT entry handle.
|
||||
*/
|
||||
public static native int getEntry(int inst, String key);
|
||||
|
||||
/**
|
||||
* Returns NT entry handle.
|
||||
*
|
||||
* @param topic NT entry topic.
|
||||
* @param type NT entry type.
|
||||
* @param typeStr NT entry type as a string.
|
||||
* @param options NT entry pubsub options.
|
||||
* @return NT entry handle.
|
||||
*/
|
||||
public static int getEntry(
|
||||
int topic, int type, String typeStr, PubSubOptions options) {
|
||||
return getEntryImpl(topic, type, typeStr, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns NT entry handle.
|
||||
*
|
||||
* @param topic NT entry topic.
|
||||
* @param type NT entry type.
|
||||
* @param typeStr NT entry type as a string.
|
||||
* @param options NT entry pubsub options.
|
||||
* @return NT entry handle.
|
||||
*/
|
||||
public static int getEntry(
|
||||
int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return getEntryImpl(topic, type, typeStr, buildOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns NT entry name.
|
||||
*
|
||||
* @param entry NT entry handle.
|
||||
* @return NT entry name.
|
||||
*/
|
||||
public static native String getEntryName(int entry);
|
||||
|
||||
/**
|
||||
* Returns NT entry last change time in microseconds.
|
||||
*
|
||||
* @param entry NT entry handle.
|
||||
* @return NT entry last change time in microseconds.
|
||||
*/
|
||||
public static native long getEntryLastChange(int entry);
|
||||
|
||||
/**
|
||||
* Returns NT entry type.
|
||||
*
|
||||
* @param entry NT entry handle.
|
||||
* @return NT entry type.
|
||||
*/
|
||||
public static native int getType(int entry);
|
||||
|
||||
/* Topic functions */
|
||||
|
||||
/**
|
||||
* Returns list of topic handles.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param prefix Topic prefix.
|
||||
* @param types Topic types.
|
||||
* @return List of topic handles.
|
||||
*/
|
||||
public static native int[] getTopics(int inst, String prefix, int types);
|
||||
|
||||
/**
|
||||
* Returns list of topic handles.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param prefix Topic prefix.
|
||||
* @param types Topic types as strings.
|
||||
* @return List of topic handles.
|
||||
*/
|
||||
public static native int[] getTopicsStr(int inst, String prefix, String[] types);
|
||||
|
||||
/**
|
||||
* Returns list of topic infos.
|
||||
*
|
||||
* @param instObject NT instance.
|
||||
* @param inst NT instance handle.
|
||||
* @param prefix Topic prefix.
|
||||
* @param types Topic types.
|
||||
* @return List of topic infos.
|
||||
*/
|
||||
public static native TopicInfo[] getTopicInfos(
|
||||
NetworkTableInstance instObject, int inst, String prefix, int types);
|
||||
|
||||
/**
|
||||
* Returns list of topic infos.
|
||||
*
|
||||
* @param instObject NT instance.
|
||||
* @param inst NT instance handle.
|
||||
* @param prefix Topic prefix.
|
||||
* @param types Topic types as strings.
|
||||
* @return List of topic infos.
|
||||
*/
|
||||
public static native TopicInfo[] getTopicInfosStr(
|
||||
NetworkTableInstance instObject, int inst, String prefix, String[] types);
|
||||
|
||||
/**
|
||||
* Returns Topic handle.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param name Topic name.
|
||||
* @return Topic handle.
|
||||
*/
|
||||
public static native int getTopic(int inst, String name);
|
||||
|
||||
/**
|
||||
* Returns topic name.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return Topic name.
|
||||
*/
|
||||
public static native String getTopicName(int topic);
|
||||
|
||||
/**
|
||||
* Returns topic type.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return Topic type.
|
||||
*/
|
||||
public static native int getTopicType(int topic);
|
||||
|
||||
/**
|
||||
* Sets topic persistency.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param value True if topic should be persistent.
|
||||
*/
|
||||
public static native void setTopicPersistent(int topic, boolean value);
|
||||
|
||||
/**
|
||||
* Returns true if topic is persistent.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return True if topic is persistent.
|
||||
*/
|
||||
public static native boolean getTopicPersistent(int topic);
|
||||
|
||||
/**
|
||||
* Sets whether topic is retained.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param value True if topic should be retained.
|
||||
*/
|
||||
public static native void setTopicRetained(int topic, boolean value);
|
||||
|
||||
/**
|
||||
* Returns true if topic is retained.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return True if topic is retained.
|
||||
*/
|
||||
public static native boolean getTopicRetained(int topic);
|
||||
|
||||
/**
|
||||
* Sets topic caching.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param value True if topic should be cached.
|
||||
*/
|
||||
public static native void setTopicCached(int topic, boolean value);
|
||||
|
||||
/**
|
||||
* Returns true if topic is cached.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return True if topic is cached.
|
||||
*/
|
||||
public static native boolean getTopicCached(int topic);
|
||||
|
||||
/**
|
||||
* Returns topic type as string.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return Topic type as string.
|
||||
*/
|
||||
public static native String getTopicTypeString(int topic);
|
||||
|
||||
/**
|
||||
* Returns true if topic exists.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return True if topic exists.
|
||||
*/
|
||||
public static native boolean getTopicExists(int topic);
|
||||
|
||||
/**
|
||||
* Returns topic property.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param name Property name.
|
||||
* @return Topic property.
|
||||
*/
|
||||
public static native String getTopicProperty(int topic, String name);
|
||||
|
||||
/**
|
||||
* Sets topic property.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param name Property name.
|
||||
* @param value Property value.
|
||||
*/
|
||||
public static native void setTopicProperty(int topic, String name, String value);
|
||||
|
||||
/**
|
||||
* Deletes topic property.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param name Property name.
|
||||
*/
|
||||
public static native void deleteTopicProperty(int topic, String name);
|
||||
|
||||
/**
|
||||
* Returns topic properties.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @return Topic properties.
|
||||
*/
|
||||
public static native String getTopicProperties(int topic);
|
||||
|
||||
/**
|
||||
* Sets topic properties.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param properties Topic properties.
|
||||
*/
|
||||
public static native void setTopicProperties(int topic, String properties);
|
||||
|
||||
/**
|
||||
* Subscribes to topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param options Pubsub options.
|
||||
* @return Subscriber handle.
|
||||
*/
|
||||
public static native int subscribe(
|
||||
int topic, int type, String typeStr, PubSubOptions options);
|
||||
|
||||
/**
|
||||
* Subscribes to topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param options Pubsub options.
|
||||
* @return Subscriber handle.
|
||||
*/
|
||||
public static int subscribe(
|
||||
int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return subscribe(topic, type, typeStr, buildOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from topic.
|
||||
*
|
||||
* @param sub Subscriber handle.
|
||||
*/
|
||||
public static native void unsubscribe(int sub);
|
||||
|
||||
/**
|
||||
* Publishes topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param options Pubsub options.
|
||||
* @return Publish handle.
|
||||
*/
|
||||
public static native int publish(
|
||||
int topic, int type, String typeStr, PubSubOptions options);
|
||||
|
||||
/**
|
||||
* Publishes topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param options Pubsub options.
|
||||
* @return Publish handle.
|
||||
*/
|
||||
public static int publish(
|
||||
int topic, int type, String typeStr, PubSubOption... options) {
|
||||
return publish(topic, type, typeStr, buildOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param properties Topic properties.
|
||||
* @param options Pubsub options.
|
||||
* @return Publish handle.
|
||||
*/
|
||||
public static native int publishEx(
|
||||
int topic, int type, String typeStr, String properties, PubSubOptions options);
|
||||
|
||||
/**
|
||||
* Publishes topic.
|
||||
*
|
||||
* @param topic Topic handle.
|
||||
* @param type Topic type.
|
||||
* @param typeStr Topic type as a string.
|
||||
* @param properties Topic properties.
|
||||
* @param options Pubsub options.
|
||||
* @return Publish handle.
|
||||
*/
|
||||
public static int publishEx(
|
||||
int topic, int type, String typeStr, String properties, PubSubOption... options) {
|
||||
return publishEx(topic, type, typeStr, properties, buildOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpublishes topic.
|
||||
*
|
||||
* @param pubentry Publish entry handle.
|
||||
*/
|
||||
public static native void unpublish(int pubentry);
|
||||
|
||||
/**
|
||||
* Releases NT entry.
|
||||
*
|
||||
* @param entry NT entry handle.
|
||||
*/
|
||||
public static native void releaseEntry(int entry);
|
||||
|
||||
/**
|
||||
* Relesaes pubsub entry.
|
||||
*
|
||||
* @param pubsubentry Pubsub entry handle.
|
||||
*/
|
||||
public static native void release(int pubsubentry);
|
||||
|
||||
/**
|
||||
* Returns topic from pubsub entry handle.
|
||||
*
|
||||
* @param pubsubentry Pubsub entry handle.
|
||||
* @return Topic handle.
|
||||
*/
|
||||
public static native int getTopicFromHandle(int pubsubentry);
|
||||
|
||||
/**
|
||||
* Subscribes to multiple topics.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param prefixes List of topic prefixes.
|
||||
* @param options Pubsub options.
|
||||
* @return Subscribe handle.
|
||||
*/
|
||||
public static native int subscribeMultiple(int inst, String[] prefixes, PubSubOptions options);
|
||||
|
||||
/**
|
||||
* Subscribes to multiple topics.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param prefixes List of topic prefixes.
|
||||
* @param options Pubsub options.
|
||||
* @return Subscribe handle.
|
||||
*/
|
||||
public static int subscribeMultiple(int inst, String[] prefixes, PubSubOption... options) {
|
||||
return subscribeMultiple(inst, prefixes, buildOptions(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from multiple topics.
|
||||
*
|
||||
* @param sub Subscribe handle.
|
||||
*/
|
||||
public static native void unsubscribeMultiple(int sub);
|
||||
{% for t in types %}
|
||||
/**
|
||||
* Returns timestamped topic value as an atomic {{ t.TypeName }}.
|
||||
*
|
||||
* @param subentry Subentry handle.
|
||||
* @param defaultValue Default value.
|
||||
* @return Timestamped topic value.
|
||||
*/
|
||||
public static native Timestamped{{ t.TypeName }} getAtomic{{ t.TypeName }}(
|
||||
int subentry, {{ t.java.ValueType }} defaultValue);
|
||||
|
||||
/**
|
||||
* Returns queued timestamped topic values.
|
||||
*
|
||||
* @param subentry Subentry handle.
|
||||
* @return List of timestamped topic values.
|
||||
*/
|
||||
public static native Timestamped{{ t.TypeName }}[] readQueue{{ t.TypeName }}(int subentry);
|
||||
|
||||
/**
|
||||
* Returns queued topic values.
|
||||
*
|
||||
* @param subentry Subentry handle.
|
||||
* @return List of topic values.
|
||||
*/
|
||||
public static native {{ t.java.ValueType }}[] readQueueValues{{ t.TypeName }}(int subentry);
|
||||
{% if t.TypeName == "Raw" %}
|
||||
/**
|
||||
* Sets raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Raw value buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setRaw(int entry, long time, byte[] value) {
|
||||
return setRaw(entry, time, value, 0, value.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Raw value buffer.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setRaw(int entry, long time, byte[] value, int start, int len);
|
||||
|
||||
/**
|
||||
* Sets raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Raw value buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setRaw(int entry, long time, ByteBuffer value) {
|
||||
int pos = value.position();
|
||||
return setRaw(entry, time, value, pos, value.capacity() - pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Raw value buffer.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setRaw(int entry, long time, ByteBuffer value, int start, int len) {
|
||||
if (value.isDirect()) {
|
||||
if (start < 0) {
|
||||
@@ -234,23 +590,84 @@ public final class NetworkTablesJNI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets raw topic value buffer.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Raw value buffer.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
private static native boolean setRawBuffer(int entry, long time, ByteBuffer value, int start, int len);
|
||||
{% else %}
|
||||
/**
|
||||
* Sets topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param value Topic value.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean set{{ t.TypeName }}(int entry, long time, {{ t.java.ValueType }} value);
|
||||
{% endif %}
|
||||
/**
|
||||
* Returns topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param defaultValue Default value.
|
||||
* @return Topic value.
|
||||
*/
|
||||
public static native {{ t.java.ValueType }} get{{ t.TypeName }}(int entry, {{ t.java.ValueType }} defaultValue);
|
||||
{% if t.TypeName == "Raw" %}
|
||||
/**
|
||||
* Sets default raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setDefaultRaw(int entry, long time, byte[] defaultValue) {
|
||||
return setDefaultRaw(entry, time, defaultValue, 0, defaultValue.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setDefaultRaw(int entry, long time, byte[] defaultValue, int start, int len);
|
||||
|
||||
/**
|
||||
* Sets default raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setDefaultRaw(int entry, long time, ByteBuffer defaultValue) {
|
||||
int pos = defaultValue.position();
|
||||
return setDefaultRaw(entry, time, defaultValue, pos, defaultValue.limit() - pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default raw topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static boolean setDefaultRaw(int entry, long time, ByteBuffer defaultValue, int start, int len) {
|
||||
if (defaultValue.isDirect()) {
|
||||
if (start < 0) {
|
||||
@@ -270,25 +687,91 @@ public final class NetworkTablesJNI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default raw topic value buffer.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @param start Value's offset into buffer.
|
||||
* @param len Length of value in buffer.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
private static native boolean setDefaultRawBuffer(int entry, long time, ByteBuffer defaultValue, int start, int len);
|
||||
{% else %}
|
||||
/**
|
||||
* Sets default topic value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param time Time in microseconds.
|
||||
* @param defaultValue Default value.
|
||||
* @return True if set succeeded.
|
||||
*/
|
||||
public static native boolean setDefault{{ t.TypeName }}(int entry, long time, {{ t.java.ValueType }} defaultValue);
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
/**
|
||||
* Returns queued subentry values.
|
||||
*
|
||||
* @param subentry Subentry handle.
|
||||
* @return List of queued subentry values.
|
||||
*/
|
||||
public static native NetworkTableValue[] readQueueValue(int subentry);
|
||||
|
||||
/**
|
||||
* Returns entry's NT value.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @return Entry's NT value.
|
||||
*/
|
||||
public static native NetworkTableValue getValue(int entry);
|
||||
|
||||
/**
|
||||
* Sets entry flags.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @param flags Entry flags.
|
||||
*/
|
||||
public static native void setEntryFlags(int entry, int flags);
|
||||
|
||||
/**
|
||||
* Returns entry flags.
|
||||
*
|
||||
* @param entry Entry handle.
|
||||
* @return Entry flags.
|
||||
*/
|
||||
public static native int getEntryFlags(int entry);
|
||||
|
||||
/**
|
||||
* Returns topic info.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param topic Topic handle.
|
||||
* @return Topic info.
|
||||
*/
|
||||
public static native TopicInfo getTopicInfo(NetworkTableInstance inst, int topic);
|
||||
|
||||
/**
|
||||
* Creates a listener poller.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @return Listener poller handle.
|
||||
*/
|
||||
public static native int createListenerPoller(int inst);
|
||||
|
||||
/**
|
||||
* Destroys listener poller.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
*/
|
||||
public static native void destroyListenerPoller(int poller);
|
||||
|
||||
/**
|
||||
* Converts NT event kinds to mask.
|
||||
*
|
||||
* @param kinds Enum set of NT event kinds.
|
||||
* @return NT event mask.
|
||||
*/
|
||||
private static int kindsToMask(EnumSet<NetworkTableEvent.Kind> kinds) {
|
||||
int mask = 0;
|
||||
for (NetworkTableEvent.Kind kind : kinds) {
|
||||
@@ -297,80 +780,316 @@ public final class NetworkTablesJNI {
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
* @param prefixes Topic prefixes.
|
||||
* @param kinds Enum set of NT event kinds.
|
||||
* @return Listener handle.
|
||||
*/
|
||||
public static int addListener(int poller, String[] prefixes, EnumSet<NetworkTableEvent.Kind> kinds) {
|
||||
return addListener(poller, prefixes, kindsToMask(kinds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
* @param handle Topic handle.
|
||||
* @param kinds Enum set of NT event kinds.
|
||||
* @return Listener handle.
|
||||
*/
|
||||
public static int addListener(int poller, int handle, EnumSet<NetworkTableEvent.Kind> kinds) {
|
||||
return addListener(poller, handle, kindsToMask(kinds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
* @param prefixes Topic prefixes.
|
||||
* @param mask NT event mask.
|
||||
* @return Listener handle.
|
||||
*/
|
||||
public static native int addListener(int poller, String[] prefixes, int mask);
|
||||
|
||||
/**
|
||||
* Adds listener.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
* @param handle Topic handle.
|
||||
* @param mask NT event mask.
|
||||
* @return Listener handle.
|
||||
*/
|
||||
public static native int addListener(int poller, int handle, int mask);
|
||||
|
||||
/**
|
||||
* Returns NT events from listener queue.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param poller Listener poller handle.
|
||||
* @return List of NT events.
|
||||
*/
|
||||
public static native NetworkTableEvent[] readListenerQueue(
|
||||
NetworkTableInstance inst, int poller);
|
||||
|
||||
/**
|
||||
* Removes listener.
|
||||
*
|
||||
* @param listener Listener handle.
|
||||
*/
|
||||
public static native void removeListener(int listener);
|
||||
|
||||
/**
|
||||
* Returns network mode.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @return Network mode.
|
||||
*/
|
||||
public static native int getNetworkMode(int inst);
|
||||
|
||||
/**
|
||||
* Starts local-only operation. Prevents calls to startServer or startClient from taking effect.
|
||||
* Has no effect if startServer or startClient has already been called.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void startLocal(int inst);
|
||||
|
||||
/**
|
||||
* Stops local-only operation. startServer or startClient can be called after this call to start
|
||||
* a server or client.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void stopLocal(int inst);
|
||||
|
||||
/**
|
||||
* Starts a server using the specified filename, listening address, and port.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param persistFilename the name of the persist file to use
|
||||
* @param listenAddress the address to listen on, or empty to listen on any address
|
||||
* @param port3 port to communicate over (NT3)
|
||||
* @param port4 port to communicate over (NT4)
|
||||
*/
|
||||
public static native void startServer(
|
||||
int inst, String persistFilename, String listenAddress, int port3, int port4);
|
||||
|
||||
/**
|
||||
* Stops the server if it is running.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void stopServer(int inst);
|
||||
|
||||
/**
|
||||
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param identity network identity to advertise (cannot be empty string)
|
||||
*/
|
||||
public static native void startClient3(int inst, String identity);
|
||||
|
||||
/**
|
||||
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param identity network identity to advertise (cannot be empty string)
|
||||
*/
|
||||
public static native void startClient4(int inst, String identity);
|
||||
|
||||
/**
|
||||
* Stops the client if it is running.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void stopClient(int inst);
|
||||
|
||||
/**
|
||||
* Sets server address and port for client (without restarting client).
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param serverName server name
|
||||
* @param port port to communicate over
|
||||
*/
|
||||
public static native void setServer(int inst, String serverName, int port);
|
||||
|
||||
/**
|
||||
* Sets server addresses and ports for client (without restarting client). The client will
|
||||
* attempt to connect to each server in round robin fashion.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param serverNames array of server names
|
||||
* @param ports array of port numbers (0=default)
|
||||
*/
|
||||
public static native void setServer(int inst, String[] serverNames, int[] ports);
|
||||
|
||||
/**
|
||||
* Sets server addresses and port for client (without restarting client). Connects using commonly
|
||||
* known robot addresses for the specified team.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param team team number
|
||||
* @param port port to communicate over
|
||||
*/
|
||||
public static native void setServerTeam(int inst, int team, int port);
|
||||
|
||||
/**
|
||||
* Disconnects the client if it's running and connected. This will automatically start
|
||||
* reconnection attempts to the current server list.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void disconnect(int inst);
|
||||
|
||||
/**
|
||||
* Starts requesting server address from Driver Station. This connects to the Driver Station
|
||||
* running on localhost to obtain the server IP address.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param port server port to use in combination with IP from DS
|
||||
*/
|
||||
public static native void startDSClient(int inst, int port);
|
||||
|
||||
/**
|
||||
* Stops requesting server address from Driver Station.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void stopDSClient(int inst);
|
||||
|
||||
/**
|
||||
* Flushes all updated values immediately to the local client/server. This does not flush to the
|
||||
* network.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void flushLocal(int inst);
|
||||
|
||||
/**
|
||||
* Flushes all updated values immediately to the network. Note: This is rate-limited to protect
|
||||
* the network from flooding. This is primarily useful for synchronizing network updates with
|
||||
* user code.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
*/
|
||||
public static native void flush(int inst);
|
||||
|
||||
/**
|
||||
* Gets information on the currently established network connections. If operating as a client,
|
||||
* this will return either zero or one values.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @return array of connection information
|
||||
*/
|
||||
public static native ConnectionInfo[] getConnections(int inst);
|
||||
|
||||
/**
|
||||
* Return whether or not the instance is connected to another node.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @return True if connected.
|
||||
*/
|
||||
public static native boolean isConnected(int inst);
|
||||
|
||||
/**
|
||||
* Get the time offset between server time and local time. Add this value to local time to get
|
||||
* the estimated equivalent server time. In server mode, this always returns 0. In client mode,
|
||||
* this returns the time offset only if the client and server are connected and have exchanged
|
||||
* synchronization messages. Note the time offset may change over time as it is periodically
|
||||
* updated; to receive updates as events, add a listener to the "time sync" event.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @return Time offset in microseconds (optional)
|
||||
*/
|
||||
public static native OptionalLong getServerTimeOffset(int inst);
|
||||
|
||||
/**
|
||||
* Returns the current timestamp in microseconds.
|
||||
*
|
||||
* @return The current timestsamp in microseconds.
|
||||
*/
|
||||
public static native long now();
|
||||
|
||||
/**
|
||||
* Starts logging entry changes to a DataLog.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param log data log handle; lifetime must extend until StopEntryDataLog is called or the
|
||||
* instance is destroyed
|
||||
* @param prefix only store entries with names that start with this prefix; the prefix is not
|
||||
* included in the data log entry name
|
||||
* @param logPrefix prefix to add to data log entry names
|
||||
* @return Data logger handle
|
||||
*/
|
||||
private static native int startEntryDataLog(int inst, long log, String prefix, String logPrefix);
|
||||
|
||||
/**
|
||||
* Starts logging entry changes to a DataLog.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param log data log object; lifetime must extend until StopEntryDataLog is called or the
|
||||
* instance is destroyed
|
||||
* @param prefix only store entries with names that start with this prefix; the prefix is not
|
||||
* included in the data log entry name
|
||||
* @param logPrefix prefix to add to data log entry names
|
||||
* @return Data logger handle
|
||||
*/
|
||||
public static int startEntryDataLog(int inst, DataLog log, String prefix, String logPrefix) {
|
||||
return startEntryDataLog(inst, log.getImpl(), prefix, logPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops logging entry changes to a DataLog.
|
||||
*
|
||||
* @param logger data logger handle
|
||||
*/
|
||||
public static native void stopEntryDataLog(int logger);
|
||||
|
||||
/**
|
||||
* Starts logging connection changes to a DataLog.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param log data log handle; lifetime must extend until StopConnectionDataLog is called or the
|
||||
* instance is destroyed
|
||||
* @param name data log entry name
|
||||
* @return Data logger handle
|
||||
*/
|
||||
private static native int startConnectionDataLog(int inst, long log, String name);
|
||||
|
||||
/**
|
||||
* Starts logging connection changes to a DataLog.
|
||||
*
|
||||
* @param inst NT instance handle.
|
||||
* @param log data log object; lifetime must extend until StopConnectionDataLog is called or the
|
||||
* instance is destroyed
|
||||
* @param name data log entry name
|
||||
* @return Data logger handle
|
||||
*/
|
||||
public static int startConnectionDataLog(int inst, DataLog log, String name) {
|
||||
return startConnectionDataLog(inst, log.getImpl(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops logging connection changes to a DataLog.
|
||||
*
|
||||
* @param logger data logger handle
|
||||
*/
|
||||
public static native void stopConnectionDataLog(int logger);
|
||||
|
||||
/**
|
||||
* Add logger callback function. By default, log messages are sent to stderr; this function sends
|
||||
* log messages with the specified levels to the provided callback function instead. The callback
|
||||
* function will only be called for log messages with level greater than or equal to minLevel and
|
||||
* less than or equal to maxLevel; messages outside this range will be silently ignored.
|
||||
*
|
||||
* @param poller Listener poller handle.
|
||||
* @param minLevel minimum log level
|
||||
* @param maxLevel maximum log level
|
||||
* @return Listener handle
|
||||
*/
|
||||
public static native int addLogger(int poller, int minLevel, int maxLevel);
|
||||
|
||||
/** Utility class. */
|
||||
|
||||
@@ -517,6 +517,12 @@ public interface GenericPublisher extends Publisher, Consumer<NetworkTableValue>
|
||||
*/
|
||||
boolean setDefaultBooleanArray(boolean[] defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
boolean setDefaultBooleanArray(Boolean[] defaultValue);
|
||||
|
||||
|
||||
@@ -528,6 +534,12 @@ public interface GenericPublisher extends Publisher, Consumer<NetworkTableValue>
|
||||
*/
|
||||
boolean setDefaultIntegerArray(long[] defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
boolean setDefaultIntegerArray(Long[] defaultValue);
|
||||
|
||||
|
||||
@@ -539,6 +551,12 @@ public interface GenericPublisher extends Publisher, Consumer<NetworkTableValue>
|
||||
*/
|
||||
boolean setDefaultFloatArray(float[] defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
boolean setDefaultFloatArray(Float[] defaultValue);
|
||||
|
||||
|
||||
@@ -550,6 +568,12 @@ public interface GenericPublisher extends Publisher, Consumer<NetworkTableValue>
|
||||
*/
|
||||
boolean setDefaultDoubleArray(double[] defaultValue);
|
||||
|
||||
/**
|
||||
* Sets the entry's value if it does not exist.
|
||||
*
|
||||
* @param defaultValue the default value to set
|
||||
* @return False if the entry exists with a different type
|
||||
*/
|
||||
boolean setDefaultDoubleArray(Double[] defaultValue);
|
||||
|
||||
|
||||
|
||||
@@ -1009,5 +1009,5 @@ public final class NetworkTableEntry implements Publisher, Subscriber {
|
||||
}
|
||||
|
||||
private final Topic m_topic;
|
||||
protected int m_handle;
|
||||
private final int m_handle;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ public final class NetworkTableInstance implements AutoCloseable {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the network mode value.
|
||||
*
|
||||
* @return The network mode value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,5 +40,6 @@ public abstract class EntryBase implements Subscriber, Publisher {
|
||||
return NetworkTablesJNI.getEntryLastChange(m_handle);
|
||||
}
|
||||
|
||||
/** NetworkTables handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -7,16 +7,31 @@ package edu.wpi.first.networktables;
|
||||
/** NetworkTables log message. */
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class LogMessage {
|
||||
/** Logging levels. */
|
||||
/** Critical logging level. */
|
||||
public static final int kCritical = 50;
|
||||
|
||||
/** Error logging level. */
|
||||
public static final int kError = 40;
|
||||
|
||||
/** Warning log level. */
|
||||
public static final int kWarning = 30;
|
||||
|
||||
/** Info log level. */
|
||||
public static final int kInfo = 20;
|
||||
|
||||
/** Debug log level. */
|
||||
public static final int kDebug = 10;
|
||||
|
||||
/** Debug log level 1. */
|
||||
public static final int kDebug1 = 9;
|
||||
|
||||
/** Debug log level 2. */
|
||||
public static final int kDebug2 = 8;
|
||||
|
||||
/** Debug log level 3. */
|
||||
public static final int kDebug3 = 7;
|
||||
|
||||
/** Debug log level 4. */
|
||||
public static final int kDebug4 = 6;
|
||||
|
||||
/** Log level of the message. */
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.networktables;
|
||||
|
||||
import edu.wpi.first.util.sendable.SendableBuilder;
|
||||
|
||||
/** Helper class for building Sendable dashboard representations for NetworkTables. */
|
||||
public interface NTSendableBuilder extends SendableBuilder {
|
||||
/**
|
||||
* Set the function that should be called to update the network table for things other than
|
||||
|
||||
@@ -330,7 +330,7 @@ public final class NetworkTable {
|
||||
* @return true if the table as a value assigned to the given key
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return !("".equals(key)) && getTopic(key).exists();
|
||||
return !"".equals(key) && getTopic(key).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ package edu.wpi.first.networktables;
|
||||
*/
|
||||
@SuppressWarnings("MemberName")
|
||||
public final class NetworkTableEvent {
|
||||
/** NetworkTable event kind. */
|
||||
public enum Kind {
|
||||
/**
|
||||
* Initial listener addition. Set this to receive immediate notification of matches to other
|
||||
@@ -61,6 +62,11 @@ public final class NetworkTableEvent {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NetworkTable event kind value.
|
||||
*
|
||||
* @return The NetworkTable event kind value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -39,10 +39,20 @@ public enum NetworkTableType {
|
||||
m_valueStr = valueStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NetworkTable type value.
|
||||
*
|
||||
* @return The NetworkTable type value.
|
||||
*/
|
||||
public int getValue() {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the NetworkTable type value as as string.
|
||||
*
|
||||
* @return The NetworkTable type value as a string.
|
||||
*/
|
||||
public String getValueStr() {
|
||||
return m_valueStr;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,11 @@ public final class ProtobufTopic<T> extends Topic {
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the protobuf.
|
||||
*
|
||||
* @return The protobuf.
|
||||
*/
|
||||
public Protobuf<T, ?> getProto() {
|
||||
return m_proto;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,11 @@ public final class StructArrayTopic<T> extends Topic {
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the struct.
|
||||
*
|
||||
* @return The struct.
|
||||
*/
|
||||
public Struct<T> getStruct() {
|
||||
return m_struct;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,11 @@ public final class StructTopic<T> extends Topic {
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the struct.
|
||||
*
|
||||
* @return The struct.
|
||||
*/
|
||||
public Struct<T> getStruct() {
|
||||
return m_struct;
|
||||
}
|
||||
|
||||
@@ -336,6 +336,9 @@ public class Topic {
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
/** NetworkTables instance. */
|
||||
protected NetworkTableInstance m_inst;
|
||||
|
||||
/** NetworkTables handle. */
|
||||
protected int m_handle;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,17 @@ bool LocalStorage::MultiSubscriberData::Matches(std::string_view name,
|
||||
}
|
||||
|
||||
int LocalStorage::DataLoggerData::Start(TopicData* topic, int64_t time) {
|
||||
std::string_view typeStr = topic->typeStr;
|
||||
// NT and DataLog use different standard representations for int and int[]
|
||||
if (typeStr == "int") {
|
||||
typeStr = "int64";
|
||||
} else if (typeStr == "int[]") {
|
||||
typeStr = "int64[]";
|
||||
}
|
||||
return log.Start(fmt::format("{}{}", logPrefix,
|
||||
wpi::drop_front(topic->name, prefix.size())),
|
||||
topic->typeStr == "int" ? "int64" : topic->typeStr,
|
||||
DataLoggerEntry::MakeMetadata(topic->propertiesStr), time);
|
||||
typeStr, DataLoggerEntry::MakeMetadata(topic->propertiesStr),
|
||||
time);
|
||||
}
|
||||
|
||||
void LocalStorage::DataLoggerEntry::Append(const Value& v) {
|
||||
|
||||
@@ -254,13 +254,13 @@ class LocalStorage final : public net::ILocalStorage {
|
||||
wpi::SmallVectorImpl<typename TypeInfo<T>::SmallElem>& buf,
|
||||
typename TypeInfo<T>::View defaultValue);
|
||||
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry) {
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry, unsigned int types) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto subscriber = m_impl.GetSubEntry(subentry);
|
||||
if (!subscriber) {
|
||||
return {};
|
||||
}
|
||||
return subscriber->pollStorage.ReadValue();
|
||||
return subscriber->pollStorage.ReadValue(types);
|
||||
}
|
||||
|
||||
template <ValidType T>
|
||||
|
||||
@@ -362,6 +362,7 @@ void NetworkClient::HandleLocal() {
|
||||
}
|
||||
|
||||
void NetworkClient::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.SetLogger(&m_logger);
|
||||
tcp.SetNoDelay(true);
|
||||
// Start the WS client
|
||||
if (m_logger.min_level() >= wpi::WPI_LOG_DEBUG4) {
|
||||
@@ -401,9 +402,8 @@ void NetworkClient::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp,
|
||||
INFO("CONNECTED NT4 to {} port {}", connInfo.remote_ip, connInfo.remote_port);
|
||||
m_connHandle = m_connList.AddConnection(connInfo);
|
||||
|
||||
m_wire =
|
||||
std::make_shared<net::WebSocketConnection>(ws, connInfo.protocol_version);
|
||||
m_wire->Start();
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(
|
||||
ws, connInfo.protocol_version, m_logger);
|
||||
m_clientImpl = std::make_unique<net::ClientImpl>(
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger, m_timeSyncUpdated,
|
||||
[this](uint32_t repeatMs) {
|
||||
|
||||
@@ -242,7 +242,7 @@ void NetworkServer::ServerConnection4::ProcessWsUpgrade() {
|
||||
m_info.protocol_version =
|
||||
protocol == "v4.1.networktables.first.wpi.edu" ? 0x0401 : 0x0400;
|
||||
m_wire = std::make_shared<net::WebSocketConnection>(
|
||||
*m_websocket, m_info.protocol_version);
|
||||
*m_websocket, m_info.protocol_version, m_logger);
|
||||
|
||||
if (protocol == "rtt.networktables.first.wpi.edu") {
|
||||
INFO("CONNECTED RTT client (from {})", m_connInfo);
|
||||
@@ -281,7 +281,6 @@ void NetworkServer::ServerConnection4::ProcessWsUpgrade() {
|
||||
INFO("CONNECTED NT4 client '{}' (from {})", dedupName, m_connInfo);
|
||||
m_info.remote_id = dedupName;
|
||||
m_server.AddConnection(this, m_info);
|
||||
m_wire->Start();
|
||||
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,
|
||||
@@ -500,6 +499,7 @@ void NetworkServer::Init() {
|
||||
if (!tcp) {
|
||||
return;
|
||||
}
|
||||
tcp->SetLogger(&m_logger);
|
||||
tcp->error.connect([logger = &m_logger](uv::Error err) {
|
||||
WPI_INFO(*logger, "NT4 socket error: {}", err.str());
|
||||
});
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
|
||||
using namespace nt;
|
||||
|
||||
std::vector<Value> ValueCircularBuffer::ReadValue() {
|
||||
std::vector<Value> ValueCircularBuffer::ReadValue(unsigned int types) {
|
||||
std::vector<Value> rv;
|
||||
rv.reserve(m_storage.size());
|
||||
for (auto&& val : m_storage) {
|
||||
if (types != 0 && (types & val.type()) == 0) {
|
||||
continue;
|
||||
}
|
||||
rv.emplace_back(std::move(val));
|
||||
}
|
||||
m_storage.reset();
|
||||
|
||||
@@ -24,7 +24,7 @@ class ValueCircularBuffer {
|
||||
m_storage.emplace_back(std::forward<Args...>(args...));
|
||||
}
|
||||
|
||||
std::vector<Value> ReadValue();
|
||||
std::vector<Value> ReadValue(unsigned int types);
|
||||
template <ValidType T>
|
||||
std::vector<Timestamped<typename TypeInfo<T>::Value>> Read();
|
||||
|
||||
|
||||
@@ -67,26 +67,28 @@ void ClientImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
DEBUG4("BinaryMessage({})", id);
|
||||
|
||||
// handle RTT ping response (only use first one)
|
||||
if (!m_haveTimeOffset && id == -1) {
|
||||
if (!value.IsInteger()) {
|
||||
WARN("RTT ping response with non-integer type {}",
|
||||
static_cast<int>(value.type()));
|
||||
continue;
|
||||
}
|
||||
DEBUG4("RTT ping response time {} value {}", value.time(),
|
||||
value.GetInteger());
|
||||
if (m_wire.GetVersion() < 0x0401) {
|
||||
m_pongTimeMs = curTimeMs;
|
||||
}
|
||||
int64_t now = wpi::Now();
|
||||
int64_t rtt2 = (now - value.GetInteger()) / 2;
|
||||
if (rtt2 < m_rtt2Us) {
|
||||
m_rtt2Us = rtt2;
|
||||
int64_t serverTimeOffsetUs = value.server_time() + rtt2 - now;
|
||||
DEBUG3("Time offset: {}", serverTimeOffsetUs);
|
||||
m_outgoing.SetTimeOffset(serverTimeOffsetUs);
|
||||
m_haveTimeOffset = true;
|
||||
m_timeSyncUpdated(serverTimeOffsetUs, m_rtt2Us, true);
|
||||
if (id == -1) {
|
||||
if (!m_haveTimeOffset) {
|
||||
if (!value.IsInteger()) {
|
||||
WARN("RTT ping response with non-integer type {}",
|
||||
static_cast<int>(value.type()));
|
||||
continue;
|
||||
}
|
||||
DEBUG4("RTT ping response time {} value {}", value.time(),
|
||||
value.GetInteger());
|
||||
if (m_wire.GetVersion() < 0x0401) {
|
||||
m_pongTimeMs = curTimeMs;
|
||||
}
|
||||
int64_t now = wpi::Now();
|
||||
int64_t rtt2 = (now - value.GetInteger()) / 2;
|
||||
if (rtt2 < m_rtt2Us) {
|
||||
m_rtt2Us = rtt2;
|
||||
int64_t serverTimeOffsetUs = value.server_time() + rtt2 - now;
|
||||
DEBUG3("Time offset: {}", serverTimeOffsetUs);
|
||||
m_outgoing.SetTimeOffset(serverTimeOffsetUs);
|
||||
m_haveTimeOffset = true;
|
||||
m_timeSyncUpdated(serverTimeOffsetUs, m_rtt2Us, true);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,14 +12,15 @@ bool NetworkPing::Send(uint64_t curTimeMs) {
|
||||
if (curTimeMs < m_nextPingTimeMs) {
|
||||
return true;
|
||||
}
|
||||
// if we didn't receive a timely response to our last ping, disconnect
|
||||
uint64_t lastPing = m_wire.GetLastPingResponse();
|
||||
// if we haven't received data in a while, disconnect
|
||||
// (we should at least be getting PONG responses)
|
||||
uint64_t lastData = m_wire.GetLastReceivedTime();
|
||||
// DEBUG4("WS ping: lastPing={} curTime={} pongTimeMs={}\n", lastPing,
|
||||
// curTimeMs, m_pongTimeMs);
|
||||
if (lastPing == 0) {
|
||||
lastPing = m_pongTimeMs;
|
||||
if (lastData == 0) {
|
||||
lastData = m_pongTimeMs;
|
||||
}
|
||||
if (m_pongTimeMs != 0 && curTimeMs > (lastPing + kPingTimeoutMs)) {
|
||||
if (m_pongTimeMs != 0 && curTimeMs > (lastData + kPingTimeoutMs)) {
|
||||
m_wire.Disconnect("connection timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <span>
|
||||
|
||||
#include <wpi/Endian.h>
|
||||
#include <wpi/Logger.h>
|
||||
#include <wpi/SpanExtras.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
@@ -52,7 +53,11 @@ class WebSocketConnection::Stream final : public wpi::raw_ostream {
|
||||
void WebSocketConnection::Stream::write_impl(const char* data, size_t len) {
|
||||
if (data == m_conn.m_bufs.back().base) {
|
||||
// flush_nonempty() case
|
||||
size_t amt = len - m_conn.m_bufs.back().len;
|
||||
WPI_DEBUG4(m_conn.m_logger, "conn: writing {} bytes (nonempty)", amt);
|
||||
m_conn.m_bufs.back().len = len;
|
||||
m_conn.m_framePos += amt;
|
||||
m_conn.m_written += amt;
|
||||
if (!m_disableAlloc) {
|
||||
#ifdef NT_ENABLE_WS_FRAG
|
||||
m_conn.m_frames.back().opcode &= ~wpi::WebSocket::kFlagFin;
|
||||
@@ -74,6 +79,7 @@ void WebSocketConnection::Stream::write_impl(const char* data, size_t len) {
|
||||
size_t amt = (std::min)(static_cast<int>(kAllocSize - buf.len),
|
||||
static_cast<int>(len));
|
||||
if (amt > 0) {
|
||||
WPI_DEBUG4(m_conn.m_logger, "conn: writing {} bytes", amt);
|
||||
std::memcpy(buf.base + buf.len, data, amt);
|
||||
buf.len += amt;
|
||||
m_conn.m_framePos += amt;
|
||||
@@ -101,8 +107,9 @@ void WebSocketConnection::Stream::write_impl(const char* data, size_t len) {
|
||||
}
|
||||
|
||||
WebSocketConnection::WebSocketConnection(wpi::WebSocket& ws,
|
||||
unsigned int version)
|
||||
: m_ws{ws}, m_version{version} {}
|
||||
unsigned int version,
|
||||
wpi::Logger& logger)
|
||||
: m_ws{ws}, m_logger{logger}, m_version{version} {}
|
||||
|
||||
WebSocketConnection::~WebSocketConnection() {
|
||||
for (auto&& buf : m_bufs) {
|
||||
@@ -113,19 +120,8 @@ WebSocketConnection::~WebSocketConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketConnection::Start() {
|
||||
m_ws.pong.connect([selfweak = weak_from_this()](auto data) {
|
||||
if (data.size() != 8) {
|
||||
return;
|
||||
}
|
||||
if (auto self = selfweak.lock()) {
|
||||
self->m_lastPingResponse =
|
||||
wpi::support::endian::read64<wpi::support::native>(data.data());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketConnection::SendPing(uint64_t time) {
|
||||
WPI_DEBUG4(m_logger, "conn: sending ping {}", time);
|
||||
auto buf = AllocBuf();
|
||||
buf.len = 8;
|
||||
wpi::support::endian::write64<wpi::support::native>(buf.base, time);
|
||||
@@ -142,6 +138,8 @@ void WebSocketConnection::SendPing(uint64_t time) {
|
||||
}
|
||||
|
||||
void WebSocketConnection::StartFrame(uint8_t opcode) {
|
||||
WPI_DEBUG4(m_logger, "conn: starting frame {}",
|
||||
static_cast<unsigned int>(opcode));
|
||||
m_frames.emplace_back(opcode, m_bufs.size(), m_bufs.size() + 1);
|
||||
m_bufs.emplace_back(AllocBuf());
|
||||
m_bufs.back().len = 0;
|
||||
@@ -177,6 +175,7 @@ int WebSocketConnection::Write(
|
||||
if (kind == kText) {
|
||||
os << (first ? '[' : ',');
|
||||
}
|
||||
WPI_DEBUG4(m_logger, "writing");
|
||||
writer(os);
|
||||
}
|
||||
++m_frames.back().count;
|
||||
@@ -188,6 +187,7 @@ int WebSocketConnection::Write(
|
||||
}
|
||||
|
||||
int WebSocketConnection::Flush() {
|
||||
WPI_DEBUG4(m_logger, "conn: flushing");
|
||||
m_lastFlushTime = wpi::Now();
|
||||
if (m_state == kEmpty) {
|
||||
return 0;
|
||||
@@ -252,6 +252,7 @@ void WebSocketConnection::Send(
|
||||
os << ']';
|
||||
}
|
||||
wpi::WebSocket::Frame frame{opcode, os.bufs()};
|
||||
WPI_DEBUG4(m_logger, "Send({})", static_cast<uint8_t>(opcode));
|
||||
m_ws.SendFrames({{frame}}, [selfweak = weak_from_this()](auto bufs, auto) {
|
||||
if (auto self = selfweak.lock()) {
|
||||
self->ReleaseBufs(bufs);
|
||||
@@ -265,7 +266,7 @@ void WebSocketConnection::Send(
|
||||
|
||||
void WebSocketConnection::Disconnect(std::string_view reason) {
|
||||
m_reason = reason;
|
||||
m_ws.Fail(1005, reason);
|
||||
m_ws.Fail(1001, reason);
|
||||
}
|
||||
|
||||
wpi::uv::Buffer WebSocketConnection::AllocBuf() {
|
||||
|
||||
@@ -15,19 +15,22 @@
|
||||
|
||||
#include "WireConnection.h"
|
||||
|
||||
namespace wpi {
|
||||
class Logger;
|
||||
} // namespace wpi
|
||||
|
||||
namespace nt::net {
|
||||
|
||||
class WebSocketConnection final
|
||||
: public WireConnection,
|
||||
public std::enable_shared_from_this<WebSocketConnection> {
|
||||
public:
|
||||
WebSocketConnection(wpi::WebSocket& ws, unsigned int version);
|
||||
WebSocketConnection(wpi::WebSocket& ws, unsigned int version,
|
||||
wpi::Logger& logger);
|
||||
~WebSocketConnection() override;
|
||||
WebSocketConnection(const WebSocketConnection&) = delete;
|
||||
WebSocketConnection& operator=(const WebSocketConnection&) = delete;
|
||||
|
||||
void Start();
|
||||
|
||||
unsigned int GetVersion() const final { return m_version; }
|
||||
|
||||
void SendPing(uint64_t time) final;
|
||||
@@ -51,7 +54,9 @@ class WebSocketConnection final
|
||||
|
||||
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
|
||||
|
||||
uint64_t GetLastPingResponse() const final { return m_lastPingResponse; }
|
||||
uint64_t GetLastReceivedTime() const final {
|
||||
return m_ws.GetLastReceivedTime();
|
||||
}
|
||||
|
||||
void Disconnect(std::string_view reason) final;
|
||||
|
||||
@@ -70,6 +75,7 @@ class WebSocketConnection final
|
||||
void ReleaseBufs(std::span<wpi::uv::Buffer> bufs);
|
||||
|
||||
wpi::WebSocket& m_ws;
|
||||
wpi::Logger& m_logger;
|
||||
|
||||
class Stream;
|
||||
|
||||
@@ -92,7 +98,6 @@ class WebSocketConnection final
|
||||
State m_state = kEmpty;
|
||||
std::string m_reason;
|
||||
uint64_t m_lastFlushTime = 0;
|
||||
uint64_t m_lastPingResponse = 0;
|
||||
unsigned int m_version;
|
||||
};
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class WireConnection {
|
||||
|
||||
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
|
||||
|
||||
// Gets the timestamp of the last ping we got a reply to
|
||||
virtual uint64_t GetLastPingResponse() const = 0; // in microseconds
|
||||
// Gets the timestamp of the last incoming data
|
||||
virtual uint64_t GetLastReceivedTime() const = 0; // in microseconds
|
||||
|
||||
virtual void Disconnect(std::string_view reason) = 0;
|
||||
};
|
||||
|
||||
@@ -175,11 +175,19 @@ uint64_t NT_GetEntryLastChange(NT_Entry entry) {
|
||||
}
|
||||
|
||||
void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value) {
|
||||
NT_GetEntryValueType(entry, 0, value);
|
||||
}
|
||||
|
||||
void NT_GetEntryValueType(NT_Entry entry, unsigned int types,
|
||||
struct NT_Value* value) {
|
||||
NT_InitValue(value);
|
||||
auto v = nt::GetEntryValue(entry);
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
if (types != 0 && (types & v.type()) == 0) {
|
||||
return;
|
||||
}
|
||||
ConvertToC(v, value);
|
||||
}
|
||||
|
||||
@@ -204,6 +212,11 @@ struct NT_Value* NT_ReadQueueValue(NT_Handle subentry, size_t* count) {
|
||||
return ConvertToC<NT_Value>(nt::ReadQueueValue(subentry), count);
|
||||
}
|
||||
|
||||
struct NT_Value* NT_ReadQueueValueType(NT_Handle subentry, unsigned int types,
|
||||
size_t* count) {
|
||||
return ConvertToC<NT_Value>(nt::ReadQueueValue(subentry, types), count);
|
||||
}
|
||||
|
||||
NT_Topic* NT_GetTopics(NT_Inst inst, const char* prefix, size_t prefix_len,
|
||||
unsigned int types, size_t* count) {
|
||||
auto info_v = nt::GetTopics(inst, {prefix, prefix_len}, types);
|
||||
|
||||
@@ -144,8 +144,12 @@ unsigned int GetEntryFlags(NT_Entry entry) {
|
||||
}
|
||||
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry) {
|
||||
return ReadQueueValue(subentry, 0);
|
||||
}
|
||||
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry, unsigned int types) {
|
||||
if (auto ii = InstanceImpl::GetHandle(subentry)) {
|
||||
return ii->localStorage.ReadQueueValue(subentry);
|
||||
return ii->localStorage.ReadQueueValue(subentry, types);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
|
||||
namespace nt {
|
||||
|
||||
/**
|
||||
* Helper class for building Sendable dashboard representations for
|
||||
* NetworkTables.
|
||||
*/
|
||||
class NTSendableBuilder : public wpi::SendableBuilder {
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -246,22 +246,27 @@ class NetworkTable final {
|
||||
* Gets a raw struct serialized value topic.
|
||||
*
|
||||
* @param name topic name
|
||||
* @param info optional struct type info
|
||||
* @return Topic
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
StructTopic<T> GetStructTopic(std::string_view name) const {
|
||||
return StructTopic<T>{GetTopic(name)};
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
StructTopic<T, I...> GetStructTopic(std::string_view name, I... info) const {
|
||||
return StructTopic<T, I...>{GetTopic(name), std::move(info)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a raw struct serialized array topic.
|
||||
*
|
||||
* @param name topic name
|
||||
* @param info optional struct type info
|
||||
* @return Topic
|
||||
*/
|
||||
template <wpi::StructSerializable T>
|
||||
StructArrayTopic<T> GetStructArrayTopic(std::string_view name) const {
|
||||
return StructArrayTopic<T>{GetTopic(name)};
|
||||
template <typename T, typename... I>
|
||||
requires wpi::StructSerializable<T, I...>
|
||||
StructArrayTopic<T, I...> GetStructArrayTopic(std::string_view name,
|
||||
I... info) const {
|
||||
return StructArrayTopic<T, I...>{GetTopic(name), std::move(info)...};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -396,6 +396,7 @@ class Publisher {
|
||||
Publisher() = default;
|
||||
explicit Publisher(NT_Publisher handle) : m_pubHandle{handle} {}
|
||||
|
||||
/// NetworkTables handle.
|
||||
NT_Publisher m_pubHandle{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -464,6 +464,24 @@ uint64_t NT_GetEntryLastChange(NT_Entry entry);
|
||||
*/
|
||||
void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value);
|
||||
|
||||
/**
|
||||
* Get Entry Value.
|
||||
*
|
||||
* Returns copy of current entry value.
|
||||
* Note that one of the type options is "unassigned".
|
||||
*
|
||||
* @param entry entry handle
|
||||
* @param types bitmask of NT_Type values; 0 is treated specially
|
||||
* as a "don't care"
|
||||
* @param value storage for returned entry value
|
||||
*
|
||||
* It is the caller's responsibility to free value once it's no longer
|
||||
* needed (the utility function NT_DisposeValue() is useful for this
|
||||
* purpose).
|
||||
*/
|
||||
void NT_GetEntryValueType(NT_Entry entry, unsigned int types,
|
||||
struct NT_Value* value);
|
||||
|
||||
/**
|
||||
* Set Default Entry Value.
|
||||
*
|
||||
@@ -518,6 +536,21 @@ unsigned int NT_GetEntryFlags(NT_Entry entry);
|
||||
*/
|
||||
struct NT_Value* NT_ReadQueueValue(NT_Handle subentry, size_t* count);
|
||||
|
||||
/**
|
||||
* Read Entry Queue.
|
||||
*
|
||||
* Returns new entry values since last call. The returned array must be freed
|
||||
* using NT_DisposeValueArray().
|
||||
*
|
||||
* @param subentry subscriber or entry handle
|
||||
* @param types bitmask of NT_Type values; 0 is treated specially
|
||||
* as a "don't care"
|
||||
* @param count count of items in returned array (output)
|
||||
* @return entry value array; returns NULL and count=0 if no new values
|
||||
*/
|
||||
struct NT_Value* NT_ReadQueueValueType(NT_Handle subentry, unsigned int types,
|
||||
size_t* count);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
||||
@@ -528,6 +528,18 @@ unsigned int GetEntryFlags(NT_Entry entry);
|
||||
*/
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry);
|
||||
|
||||
/**
|
||||
* Read Entry Queue.
|
||||
*
|
||||
* Returns new entry values since last call.
|
||||
*
|
||||
* @param subentry subscriber or entry handle
|
||||
* @param types bitmask of NT_Type values; 0 is treated specially
|
||||
* as a "don't care"
|
||||
* @return entry value array
|
||||
*/
|
||||
std::vector<Value> ReadQueueValue(NT_Handle subentry, unsigned int types);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,7 @@ class MockWireConnection : public WireConnection {
|
||||
MOCK_METHOD(int, Flush, (), (override));
|
||||
|
||||
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, GetLastPingResponse, (), (const, override));
|
||||
MOCK_METHOD(uint64_t, GetLastReceivedTime, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
|
||||
};
|
||||
|
||||
@@ -181,7 +181,7 @@ TEST_F(ServerImplTest, PublishLocal) {
|
||||
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // AddClient()
|
||||
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
|
||||
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, GetLastPingResponse()).WillOnce(Return(0));
|
||||
EXPECT_CALL(wire, GetLastReceivedTime()).WillOnce(Return(0));
|
||||
EXPECT_CALL(wire, SendPing(100));
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendControl()
|
||||
EXPECT_CALL(
|
||||
@@ -258,7 +258,7 @@ TEST_F(ServerImplTest, ClientSubTopicOnlyThenValue) {
|
||||
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // AddClient()
|
||||
EXPECT_CALL(setPeriodic, Call(100)); // ClientSubscribe()
|
||||
// EXPECT_CALL(wire, Flush()).WillOnce(Return(0)); // ClientSubscribe()
|
||||
EXPECT_CALL(wire, GetLastPingResponse()).WillOnce(Return(0));
|
||||
EXPECT_CALL(wire, GetLastReceivedTime()).WillOnce(Return(0));
|
||||
EXPECT_CALL(wire, SendPing(100));
|
||||
EXPECT_CALL(wire, Ready()).WillOnce(Return(true)); // SendValues()
|
||||
EXPECT_CALL(
|
||||
|
||||
@@ -62,7 +62,7 @@ static void NtInitialize() {
|
||||
auto inst = nt::GetDefaultInstance();
|
||||
auto poller = nt::CreateListenerPoller(inst);
|
||||
nt::AddPolledListener(poller, inst, NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE);
|
||||
nt::AddPolledLogger(poller, 0, 100);
|
||||
nt::AddPolledLogger(poller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([inst, poller] {
|
||||
auto win = gui::GetSystemWindow();
|
||||
if (!win) {
|
||||
|
||||
@@ -57,6 +57,8 @@ struct TeamNumberRefHolder {
|
||||
static std::unique_ptr<TeamNumberRefHolder> teamNumberRef;
|
||||
static std::unordered_map<std::string, std::pair<unsigned int, std::string>>
|
||||
foundDevices;
|
||||
static std::unordered_map<std::string, std::optional<sysid::DeviceStatus>>
|
||||
deviceStatuses;
|
||||
static wpi::Logger logger;
|
||||
static sysid::DeploySession deploySession{logger};
|
||||
static std::unique_ptr<wpi::MulticastServiceResolver> multicastResolver;
|
||||
@@ -76,7 +78,12 @@ static void FindDevices() {
|
||||
[](const auto& a) { return a.first == "MAC"; });
|
||||
if (macKey != data.txt.end()) {
|
||||
auto& mac = macKey->second;
|
||||
foundDevices[mac] = std::make_pair(data.ipv4Address, data.hostName);
|
||||
auto& foundDevice = foundDevices[mac];
|
||||
foundDevice = std::make_pair(data.ipv4Address, data.hostName);
|
||||
auto& deviceStatus = deviceStatuses[mac];
|
||||
if (!deviceStatus) {
|
||||
deploySession.GetStatus(mac, foundDevice.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,15 +153,12 @@ static void DisplayGui() {
|
||||
int macWidth = ImGui::CalcTextSize("88:88:88:88:88:88").x;
|
||||
int ipAddressWidth = ImGui::CalcTextSize("255.255.255.255").x;
|
||||
int setWidth = ImGui::CalcTextSize(" Set Team To 99999 ").x;
|
||||
int blinkWidth = ImGui::CalcTextSize(" Blink ").x;
|
||||
int rebootWidth = ImGui::CalcTextSize(" Reboot ").x;
|
||||
|
||||
minWidth = nameWidth + macWidth + ipAddressWidth + setWidth + blinkWidth +
|
||||
rebootWidth + 100;
|
||||
minWidth = nameWidth + macWidth + ipAddressWidth + setWidth + 100;
|
||||
|
||||
std::string setString = fmt::format("Set team to {}", teamNumber);
|
||||
|
||||
if (ImGui::BeginTable("Table", 6)) {
|
||||
if (ImGui::BeginTable("Table", 4)) {
|
||||
ImGui::TableSetupColumn(
|
||||
"Name",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
@@ -171,17 +175,33 @@ static void DisplayGui() {
|
||||
"Set",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
setWidth);
|
||||
ImGui::TableSetupColumn(
|
||||
"Blink",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
blinkWidth);
|
||||
ImGui::TableSetupColumn(
|
||||
"Reboot",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
rebootWidth);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto&& i : foundDevices) {
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
for (auto&& i : foundDevices) {
|
||||
std::future<int>* future = deploySession.GetFuture(i.first);
|
||||
std::future<sysid::DeviceStatus>* futureStatus =
|
||||
deploySession.GetStatusFuture(i.first);
|
||||
if (ImGui::BeginTable("Table", 4)) {
|
||||
ImGui::TableSetupColumn(
|
||||
"Name",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
nameWidth);
|
||||
ImGui::TableSetupColumn(
|
||||
"MAC Address",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
macWidth);
|
||||
ImGui::TableSetupColumn(
|
||||
"IP Address",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
ipAddressWidth);
|
||||
ImGui::TableSetupColumn(
|
||||
"Set",
|
||||
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
|
||||
setWidth);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", i.second.second.c_str());
|
||||
@@ -192,11 +212,8 @@ static void DisplayGui() {
|
||||
in.s_addr = i.second.first;
|
||||
ImGui::Text("%s", inet_ntoa(in));
|
||||
ImGui::TableNextColumn();
|
||||
std::future<int>* future = deploySession.GetFuture(i.first);
|
||||
ImGui::PushID(i.first.c_str());
|
||||
if (future) {
|
||||
ImGui::Button("Deploying");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
const auto fs = future->wait_for(std::chrono::seconds(0));
|
||||
if (fs == std::future_status::ready) {
|
||||
@@ -207,18 +224,62 @@ static void DisplayGui() {
|
||||
deploySession.ChangeTeamNumber(i.first, teamNumber, i.second.first);
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Blink")) {
|
||||
deploySession.Blink(i.first, i.second.first);
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Button("Reboot")) {
|
||||
deploySession.Reboot(i.first, i.second.first);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
ImGui::PushID(i.first.c_str());
|
||||
if (futureStatus) {
|
||||
ImGui::Text("Refreshing Status");
|
||||
const auto fs = futureStatus->wait_for(std::chrono::seconds(0));
|
||||
if (fs == std::future_status::ready) {
|
||||
deviceStatuses[i.first] = futureStatus->get();
|
||||
deploySession.DestroyStatusFuture(i.first);
|
||||
}
|
||||
} else {
|
||||
auto& deviceStatus = deviceStatuses[i.first];
|
||||
if (deviceStatus) {
|
||||
if (ImGui::Button("Refresh Status")) {
|
||||
deploySession.GetStatus(i.first, i.second.first);
|
||||
}
|
||||
std::string formatted =
|
||||
fmt::format("Image: {}", deviceStatus.value().image);
|
||||
ImGui::Text("%s", formatted.c_str());
|
||||
formatted = fmt::format("Serial Number: {}",
|
||||
deviceStatus.value().serialNumber);
|
||||
ImGui::Text("%s", formatted.c_str());
|
||||
formatted = fmt::format(
|
||||
"Web Server Status: {}",
|
||||
deviceStatus.value().webServerEnabled ? "Enabled" : "Disabled");
|
||||
ImGui::Text("%s", formatted.c_str());
|
||||
} else {
|
||||
ImGui::Text("Waiting for refresh");
|
||||
}
|
||||
}
|
||||
|
||||
if (future) {
|
||||
ImGui::Text("Deploying");
|
||||
} else {
|
||||
if (ImGui::Button("Blink")) {
|
||||
deploySession.Blink(i.first, i.second.first);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reboot")) {
|
||||
deploySession.Reboot(i.first, i.second.first);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Disable Web Server")) {
|
||||
deploySession.DisableWebServer(i.first, i.second.first);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Enable Web Server")) {
|
||||
deploySession.EnableWebServer(i.first, i.second.first);
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::Columns(6, "Devices");
|
||||
|
||||
@@ -37,6 +37,7 @@ static constexpr std::string_view kUsername = "admin";
|
||||
static constexpr std::string_view kPassword = "";
|
||||
|
||||
std::unordered_map<std::string, std::future<int>> s_outstanding;
|
||||
std::unordered_map<std::string, std::future<DeviceStatus>> s_outstandingStatus;
|
||||
|
||||
DeploySession::DeploySession(wpi::Logger& logger) : m_logger{logger} {}
|
||||
|
||||
@@ -59,6 +60,19 @@ void DeploySession::DestroyFuture(const std::string& macAddress) {
|
||||
s_outstanding.erase(macAddress);
|
||||
}
|
||||
|
||||
std::future<DeviceStatus>* DeploySession::GetStatusFuture(
|
||||
const std::string& macAddress) {
|
||||
auto itr = s_outstandingStatus.find(macAddress);
|
||||
if (itr == s_outstandingStatus.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &itr->second;
|
||||
}
|
||||
|
||||
void DeploySession::DestroyStatusFuture(const std::string& macAddress) {
|
||||
s_outstandingStatus.erase(macAddress);
|
||||
}
|
||||
|
||||
bool DeploySession::ChangeTeamNumber(const std::string& macAddress,
|
||||
int teamNumber, unsigned int ipAddress) {
|
||||
auto itr = s_outstanding.find(macAddress);
|
||||
@@ -184,3 +198,147 @@ bool DeploySession::Blink(const std::string& macAddress,
|
||||
s_outstanding[macAddress] = std::move(future);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeploySession::DisableWebServer(const std::string& macAddress,
|
||||
unsigned int ipAddress) {
|
||||
auto itr = s_outstanding.find(macAddress);
|
||||
if (itr != s_outstanding.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::future<int> future =
|
||||
std::async(std::launch::async, [this, ipAddress, mac = macAddress]() {
|
||||
// Convert to IP address.
|
||||
wpi::SmallString<16> ip;
|
||||
in_addr addr;
|
||||
addr.s_addr = ipAddress;
|
||||
wpi::uv::AddrToName(addr, &ip);
|
||||
DEBUG("Trying to establish SSH connection to {}.", ip.str());
|
||||
try {
|
||||
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
|
||||
session.Open();
|
||||
DEBUG("SSH connection to {} was successful.", ip.str());
|
||||
|
||||
SUCCESS("roboRIO Connected!");
|
||||
|
||||
try {
|
||||
session.Execute(
|
||||
"/bin/bash -c \"/etc/init.d/systemWebServer stop\"");
|
||||
session.Execute(
|
||||
"/bin/bash -c \"update-rc.d -f systemWebServer remove\"");
|
||||
session.Execute("/bin/bash -c \"sync\"");
|
||||
} catch (const SshSession::SshException& e) {
|
||||
ERROR("An exception occurred: {}", e.what());
|
||||
throw e;
|
||||
}
|
||||
} catch (const SshSession::SshException& e) {
|
||||
DEBUG("SSH connection to {} failed with {}.", ip.str(), e.what());
|
||||
throw e;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
s_outstanding[macAddress] = std::move(future);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeploySession::EnableWebServer(const std::string& macAddress,
|
||||
unsigned int ipAddress) {
|
||||
auto itr = s_outstanding.find(macAddress);
|
||||
if (itr != s_outstanding.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::future<int> future =
|
||||
std::async(std::launch::async, [this, ipAddress, mac = macAddress]() {
|
||||
// Convert to IP address.
|
||||
wpi::SmallString<16> ip;
|
||||
in_addr addr;
|
||||
addr.s_addr = ipAddress;
|
||||
wpi::uv::AddrToName(addr, &ip);
|
||||
DEBUG("Trying to establish SSH connection to {}.", ip.str());
|
||||
try {
|
||||
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
|
||||
session.Open();
|
||||
DEBUG("SSH connection to {} was successful.", ip.str());
|
||||
|
||||
SUCCESS("roboRIO Connected!");
|
||||
|
||||
try {
|
||||
session.Execute(
|
||||
"/bin/bash -c \"update-rc.d -f systemWebServer defaults\"");
|
||||
session.Execute(
|
||||
"/bin/bash -c \"/etc/init.d/systemWebServer start\"");
|
||||
session.Execute("/bin/bash -c \"sync\"");
|
||||
} catch (const SshSession::SshException& e) {
|
||||
ERROR("An exception occurred: {}", e.what());
|
||||
throw e;
|
||||
}
|
||||
} catch (const SshSession::SshException& e) {
|
||||
DEBUG("SSH connection to {} failed with {}.", ip.str(), e.what());
|
||||
throw e;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
s_outstanding[macAddress] = std::move(future);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeploySession::GetStatus(const std::string& macAddress,
|
||||
unsigned int ipAddress) {
|
||||
auto itr = s_outstandingStatus.find(macAddress);
|
||||
if (itr != s_outstandingStatus.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::future<DeviceStatus> future =
|
||||
std::async(std::launch::async, [this, ipAddress, mac = macAddress]() {
|
||||
// Convert to IP address.
|
||||
wpi::SmallString<16> ip;
|
||||
in_addr addr;
|
||||
addr.s_addr = ipAddress;
|
||||
wpi::uv::AddrToName(addr, &ip);
|
||||
DEBUG("Trying to establish SSH connection to {}.", ip.str());
|
||||
DeviceStatus status;
|
||||
try {
|
||||
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
|
||||
session.Open();
|
||||
DEBUG("SSH connection to {} was successful.", ip.str());
|
||||
|
||||
SUCCESS("roboRIO Connected!");
|
||||
|
||||
try {
|
||||
int exitStatus = 0;
|
||||
session.ExecuteResult(
|
||||
"start-stop-daemon --status -x "
|
||||
"/usr/local/natinst/share/NIWebServer/SystemWebServer",
|
||||
&exitStatus);
|
||||
status.webServerEnabled = exitStatus == 0;
|
||||
auto serialNumber = session.ExecuteResult(
|
||||
"/sbin/fw_printenv -n serial#", &exitStatus);
|
||||
if (exitStatus == 0) {
|
||||
status.serialNumber = wpi::trim(serialNumber);
|
||||
}
|
||||
auto image = session.ExecuteResult(
|
||||
"/usr/local/natinst/bin/nirtcfg --file "
|
||||
"/etc/natinst/share/scs_imagemetadata.ini --get "
|
||||
"section=ImageMetadata,token=IMAGEVERSION,value=UNKNOWN",
|
||||
&exitStatus);
|
||||
if (exitStatus == 0) {
|
||||
status.image = wpi::trim(image);
|
||||
}
|
||||
} catch (const SshSession::SshException& e) {
|
||||
ERROR("An exception occurred: {}", e.what());
|
||||
throw e;
|
||||
}
|
||||
} catch (const SshSession::SshException& e) {
|
||||
DEBUG("SSH connection to {} failed with {}.", ip.str(), e.what());
|
||||
throw e;
|
||||
}
|
||||
return status;
|
||||
});
|
||||
|
||||
s_outstandingStatus[macAddress] = std::move(future);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ namespace sysid {
|
||||
// GUI).
|
||||
static constexpr unsigned int kLogSuccess = 31;
|
||||
|
||||
struct DeviceStatus {
|
||||
bool webServerEnabled = false;
|
||||
std::string serialNumber;
|
||||
std::string image;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a single deploy session.
|
||||
*
|
||||
@@ -49,15 +55,19 @@ class DeploySession {
|
||||
|
||||
bool Blink(const std::string& macAddress, unsigned int ipAddress);
|
||||
|
||||
bool DisableWebServer(const std::string& macAddress, unsigned int ipAddress);
|
||||
|
||||
bool EnableWebServer(const std::string& macAddress, unsigned int ipAddress);
|
||||
|
||||
bool Reboot(const std::string& macAddress, unsigned int ipAddress);
|
||||
|
||||
bool GetStatus(const std::string& macAddress, unsigned int ipAddress);
|
||||
|
||||
std::future<int>* GetFuture(const std::string& macAddress);
|
||||
void DestroyFuture(const std::string& macAddress);
|
||||
|
||||
/**
|
||||
* Returns the state of the deploy session.
|
||||
*/
|
||||
Status GetStatus() const;
|
||||
std::future<DeviceStatus>* GetStatusFuture(const std::string& macAddress);
|
||||
void DestroyStatusFuture(const std::string& macAddress);
|
||||
|
||||
private:
|
||||
// Logger reference where log messages will be sent.
|
||||
|
||||
@@ -86,13 +86,28 @@ void SshSession::Execute(std::string_view cmd) {
|
||||
ssh_channel_free(channel);
|
||||
throw SshException(ssh_get_error(m_session));
|
||||
}
|
||||
INFO("{}", cmd);
|
||||
INFO("{} {}", ssh_channel_get_exit_status(channel), cmd);
|
||||
|
||||
// Log output.
|
||||
char buf[512];
|
||||
int read = ssh_channel_read(channel, buf, sizeof(buf), 0);
|
||||
if (read != 0) {
|
||||
INFO("{}", cmd);
|
||||
if (read < static_cast<int>(sizeof(buf) / sizeof(buf[0]))) {
|
||||
buf[read] = 0;
|
||||
} else {
|
||||
buf[(sizeof(buf) / sizeof(buf[0])) - 1] = 0;
|
||||
}
|
||||
INFO("stdout: {} {}", read, buf);
|
||||
}
|
||||
|
||||
read = ssh_channel_read(channel, buf, sizeof(buf), 1);
|
||||
if (read != 0) {
|
||||
if (read < static_cast<int>(sizeof(buf) / sizeof(buf[0]))) {
|
||||
buf[read] = 0;
|
||||
} else {
|
||||
buf[(sizeof(buf) / sizeof(buf[0])) - 1] = 0;
|
||||
}
|
||||
INFO("stderr: {} {}", read, buf);
|
||||
}
|
||||
|
||||
// Close and free channel.
|
||||
@@ -100,6 +115,64 @@ void SshSession::Execute(std::string_view cmd) {
|
||||
ssh_channel_free(channel);
|
||||
}
|
||||
|
||||
std::string SshSession::ExecuteResult(std::string_view cmd, int* exitStatus) {
|
||||
// Allocate a new channel.
|
||||
ssh_channel channel = ssh_channel_new(m_session);
|
||||
if (!channel) {
|
||||
throw SshException(ssh_get_error(m_session));
|
||||
}
|
||||
|
||||
// Open the channel.
|
||||
int rc = ssh_channel_open_session(channel);
|
||||
if (rc != SSH_OK) {
|
||||
throw SshException(ssh_get_error(m_session));
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
std::string command{cmd};
|
||||
rc = ssh_channel_request_exec(channel, command.c_str());
|
||||
if (rc != SSH_OK) {
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
throw SshException(ssh_get_error(m_session));
|
||||
}
|
||||
INFO("{} {}", ssh_channel_get_exit_status(channel), cmd);
|
||||
|
||||
std::string result;
|
||||
if (exitStatus) {
|
||||
*exitStatus = ssh_channel_get_exit_status(channel);
|
||||
}
|
||||
|
||||
// Log output.
|
||||
char buf[512];
|
||||
int read = ssh_channel_read(channel, buf, sizeof(buf), 0);
|
||||
if (read != 0) {
|
||||
if (read < static_cast<int>(sizeof(buf) / sizeof(buf[0]))) {
|
||||
buf[read] = 0;
|
||||
} else {
|
||||
buf[(sizeof(buf) / sizeof(buf[0])) - 1] = 0;
|
||||
}
|
||||
result = buf;
|
||||
INFO("stdout: {} {}", read, buf);
|
||||
}
|
||||
|
||||
read = ssh_channel_read(channel, buf, sizeof(buf), 1);
|
||||
if (read != 0) {
|
||||
if (read < static_cast<int>(sizeof(buf) / sizeof(buf[0]))) {
|
||||
buf[read] = 0;
|
||||
} else {
|
||||
buf[(sizeof(buf) / sizeof(buf[0])) - 1] = 0;
|
||||
}
|
||||
INFO("stderr: {} {}", read, buf);
|
||||
}
|
||||
|
||||
// Close and free channel.
|
||||
ssh_channel_close(channel);
|
||||
ssh_channel_free(channel);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SshSession::Put(std::string_view path, std::string_view contents) {
|
||||
// Allocate the SFTP session.
|
||||
sftp_session sftp = sftp_new(m_session);
|
||||
|
||||
@@ -58,6 +58,8 @@ class SshSession {
|
||||
*/
|
||||
void Execute(std::string_view cmd);
|
||||
|
||||
std::string ExecuteResult(std::string_view cmd, int* exitStatus);
|
||||
|
||||
/**
|
||||
* Puts a file on the server using SFTP.
|
||||
*
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void Application(std::string_view saveDir);
|
||||
|
||||
#ifdef _WIN32
|
||||
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
|
||||
int nCmdShow) {
|
||||
int argc = __argc;
|
||||
char** argv = __argv;
|
||||
#else
|
||||
int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string_view saveDir;
|
||||
if (argc == 2) {
|
||||
saveDir = argv[1];
|
||||
}
|
||||
|
||||
Application(saveDir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -227,37 +227,25 @@ class FMSSimModel : public glass::FMSModel {
|
||||
glass::DataSource* GetAutonomousData() override { return &m_autonomous; }
|
||||
std::string_view GetGameSpecificMessage(
|
||||
wpi::SmallVectorImpl<char>& buf) override {
|
||||
HAL_MatchInfo info;
|
||||
HALSIM_GetMatchInfo(&info);
|
||||
buf.clear();
|
||||
buf.append(info.gameSpecificMessage,
|
||||
info.gameSpecificMessage + info.gameSpecificMessageSize);
|
||||
return std::string_view(buf.begin(), buf.size());
|
||||
return m_gameMessage;
|
||||
}
|
||||
|
||||
void SetFmsAttached(bool val) override {
|
||||
HALSIM_SetDriverStationFmsAttached(val);
|
||||
}
|
||||
void SetDsAttached(bool val) override {
|
||||
HALSIM_SetDriverStationDsAttached(val);
|
||||
}
|
||||
void SetFmsAttached(bool val) override { m_fmsAttached.SetValue(val); }
|
||||
void SetDsAttached(bool val) override { m_dsAttached.SetValue(val); }
|
||||
void SetAllianceStationId(int val) override {
|
||||
HALSIM_SetDriverStationAllianceStationId(
|
||||
static_cast<HAL_AllianceStationID>(val));
|
||||
}
|
||||
void SetMatchTime(double val) override {
|
||||
HALSIM_SetDriverStationMatchTime(val);
|
||||
}
|
||||
void SetEStop(bool val) override { HALSIM_SetDriverStationEStop(val); }
|
||||
void SetEnabled(bool val) override { HALSIM_SetDriverStationEnabled(val); }
|
||||
void SetTest(bool val) override { HALSIM_SetDriverStationTest(val); }
|
||||
void SetAutonomous(bool val) override {
|
||||
HALSIM_SetDriverStationAutonomous(val);
|
||||
m_allianceStationId.SetValue(val);
|
||||
}
|
||||
void SetMatchTime(double val) override { m_matchTime.SetValue(val); }
|
||||
void SetEStop(bool val) override { m_estop.SetValue(val); }
|
||||
void SetEnabled(bool val) override { m_enabled.SetValue(val); }
|
||||
void SetTest(bool val) override { m_test.SetValue(val); }
|
||||
void SetAutonomous(bool val) override { m_autonomous.SetValue(val); }
|
||||
void SetGameSpecificMessage(std::string_view val) override {
|
||||
HALSIM_SetGameSpecificMessage(val.data(), val.size());
|
||||
m_gameMessage = val;
|
||||
}
|
||||
|
||||
void UpdateHAL();
|
||||
|
||||
void Update() override;
|
||||
|
||||
bool Exists() override { return true; }
|
||||
@@ -274,6 +262,7 @@ class FMSSimModel : public glass::FMSModel {
|
||||
glass::DataSource m_test{"FMS:TestMode"};
|
||||
glass::DataSource m_autonomous{"FMS:AutonomousMode"};
|
||||
double m_startMatchTime = -1.0;
|
||||
std::string m_gameMessage;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -1019,6 +1008,21 @@ void RobotJoystick::GetHAL(int i) {
|
||||
HALSIM_GetJoystickPOVs(i, &data.povs);
|
||||
}
|
||||
|
||||
static void DriverStationConnect(bool enabled, bool autonomous, bool test) {
|
||||
if (!HALSIM_GetDriverStationDsAttached()) {
|
||||
// initialize FMS bits too
|
||||
gFMSModel->SetDsAttached(true);
|
||||
gFMSModel->SetEnabled(enabled);
|
||||
gFMSModel->SetAutonomous(autonomous);
|
||||
gFMSModel->SetTest(test);
|
||||
gFMSModel->UpdateHAL();
|
||||
} else {
|
||||
HALSIM_SetDriverStationEnabled(enabled);
|
||||
HALSIM_SetDriverStationAutonomous(autonomous);
|
||||
HALSIM_SetDriverStationTest(test);
|
||||
}
|
||||
}
|
||||
|
||||
static void DriverStationExecute() {
|
||||
// update sources
|
||||
for (int i = 0; i < HAL_kMaxJoysticks; ++i) {
|
||||
@@ -1072,6 +1076,7 @@ static void DriverStationExecute() {
|
||||
joy.Update();
|
||||
}
|
||||
|
||||
bool isAttached = HALSIM_GetDriverStationDsAttached();
|
||||
bool isEnabled = HALSIM_GetDriverStationEnabled();
|
||||
bool isAuto = HALSIM_GetDriverStationAutonomous();
|
||||
bool isTest = HALSIM_GetDriverStationTest();
|
||||
@@ -1100,38 +1105,45 @@ static void DriverStationExecute() {
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2{5, 20}, ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Robot State", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
if (ImGui::Selectable("Disabled", !isEnabled) || disableHotkey) {
|
||||
if (ImGui::Selectable("Disconnected", !isAttached)) {
|
||||
HALSIM_SetDriverStationEnabled(false);
|
||||
HALSIM_SetDriverStationDsAttached(false);
|
||||
isAttached = false;
|
||||
gFMSModel->Update();
|
||||
}
|
||||
if (ImGui::Selectable("Autonomous", isEnabled && isAuto && !isTest)) {
|
||||
HALSIM_SetDriverStationAutonomous(true);
|
||||
HALSIM_SetDriverStationTest(false);
|
||||
HALSIM_SetDriverStationEnabled(true);
|
||||
if (ImGui::Selectable("Disabled", isAttached && !isEnabled) ||
|
||||
disableHotkey) {
|
||||
DriverStationConnect(false, false, false);
|
||||
}
|
||||
if (ImGui::Selectable("Teleoperated", isEnabled && !isAuto && !isTest) ||
|
||||
if (ImGui::Selectable("Autonomous",
|
||||
isAttached && isEnabled && isAuto && !isTest)) {
|
||||
DriverStationConnect(true, true, false);
|
||||
}
|
||||
if (ImGui::Selectable("Teleoperated",
|
||||
isAttached && isEnabled && !isAuto && !isTest) ||
|
||||
enableHotkey) {
|
||||
HALSIM_SetDriverStationAutonomous(false);
|
||||
HALSIM_SetDriverStationTest(false);
|
||||
HALSIM_SetDriverStationEnabled(true);
|
||||
DriverStationConnect(true, false, false);
|
||||
}
|
||||
if (ImGui::Selectable("Test", isEnabled && isTest)) {
|
||||
HALSIM_SetDriverStationAutonomous(false);
|
||||
HALSIM_SetDriverStationTest(true);
|
||||
HALSIM_SetDriverStationEnabled(true);
|
||||
DriverStationConnect(true, false, true);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Update HAL
|
||||
for (int i = 0, end = gRobotJoysticks.size();
|
||||
i < end && i < HAL_kMaxJoysticks; ++i) {
|
||||
gRobotJoysticks[i].SetHAL(i);
|
||||
if (isAttached) {
|
||||
for (int i = 0, end = gRobotJoysticks.size();
|
||||
i < end && i < HAL_kMaxJoysticks; ++i) {
|
||||
gRobotJoysticks[i].SetHAL(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Send new data every 20 ms (may be slower depending on GUI refresh rate)
|
||||
static double lastNewDataTime = 0.0;
|
||||
if ((curTime - lastNewDataTime) > 0.02 && !HALSIM_IsTimingPaused()) {
|
||||
if ((curTime - lastNewDataTime) > 0.02 && !HALSIM_IsTimingPaused() &&
|
||||
isAttached) {
|
||||
lastNewDataTime = curTime;
|
||||
gFMSModel->Update();
|
||||
HALSIM_NotifyDriverStationNewData();
|
||||
}
|
||||
}
|
||||
@@ -1144,6 +1156,20 @@ FMSSimModel::FMSSimModel() {
|
||||
m_test.SetDigital(true);
|
||||
m_autonomous.SetDigital(true);
|
||||
m_matchTime.SetValue(-1.0);
|
||||
m_allianceStationId.SetValue(HAL_AllianceStationID_kRed1);
|
||||
}
|
||||
|
||||
void FMSSimModel::UpdateHAL() {
|
||||
HALSIM_SetDriverStationFmsAttached(m_fmsAttached.GetValue());
|
||||
HALSIM_SetDriverStationAllianceStationId(
|
||||
static_cast<HAL_AllianceStationID>(m_allianceStationId.GetValue()));
|
||||
HALSIM_SetDriverStationEStop(m_estop.GetValue());
|
||||
HALSIM_SetDriverStationEnabled(m_enabled.GetValue());
|
||||
HALSIM_SetDriverStationTest(m_test.GetValue());
|
||||
HALSIM_SetDriverStationAutonomous(m_autonomous.GetValue());
|
||||
HALSIM_SetDriverStationMatchTime(m_matchTime.GetValue());
|
||||
HALSIM_SetGameSpecificMessage(m_gameMessage.data(), m_gameMessage.size());
|
||||
HALSIM_SetDriverStationDsAttached(m_dsAttached.GetValue());
|
||||
}
|
||||
|
||||
void FMSSimModel::Update() {
|
||||
@@ -1176,6 +1202,11 @@ void FMSSimModel::Update() {
|
||||
m_startMatchTime = -1.0;
|
||||
}
|
||||
m_matchTime.SetValue(matchTime);
|
||||
|
||||
HAL_MatchInfo info;
|
||||
HALSIM_GetMatchInfo(&info);
|
||||
m_gameMessage.assign(info.gameSpecificMessage,
|
||||
info.gameSpecificMessage + info.gameSpecificMessageSize);
|
||||
}
|
||||
|
||||
bool FMSSimModel::IsReadOnly() {
|
||||
@@ -1387,7 +1418,6 @@ void DriverStationGui::GlobalInit() {
|
||||
gFMSModel = std::make_unique<FMSSimModel>();
|
||||
|
||||
wpi::gui::AddEarlyExecute(DriverStationExecute);
|
||||
wpi::gui::AddEarlyExecute([] { gFMSModel->Update(); });
|
||||
|
||||
storageRoot.SetCustomApply([&storageRoot] {
|
||||
gpDisableDS = &storageRoot.GetBool("disable", false);
|
||||
@@ -1433,8 +1463,13 @@ void DriverStationGui::GlobalInit() {
|
||||
win->SetDefaultSize(300, 560);
|
||||
}
|
||||
}
|
||||
if (auto win =
|
||||
dsManager->AddWindow("FMS", [] { DisplayFMS(gFMSModel.get()); })) {
|
||||
if (auto win = dsManager->AddWindow("FMS", [] {
|
||||
if (HALSIM_GetDriverStationDsAttached()) {
|
||||
DisplayFMSReadOnly(gFMSModel.get());
|
||||
} else {
|
||||
DisplayFMS(gFMSModel.get(), false);
|
||||
}
|
||||
})) {
|
||||
win->DisableRenamePopup();
|
||||
win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
|
||||
win->SetDefaultPos(5, 540);
|
||||
|
||||
14
sysid/README.md
Normal file
14
sysid/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# SysId: System Identification for Robot Mechanisms
|
||||
|
||||
## Building and Running SysId
|
||||
|
||||
See [here](../README.md#Requirements) for build requirements.
|
||||
|
||||
Run the following in the monorepo root.
|
||||
```bash
|
||||
./gradlew sysid:run
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Use [AdvantageScope](https://docs.wpilib.org/en/stable/docs/software/dashboards/advantagescope.html) (shipped with the WPILib installer) to view .wpilog files for troubleshooting when SysId fails to generate plots.
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import sys
|
||||
|
||||
# Load data
|
||||
filename = pathlib.Path(sys.argv[1])
|
||||
|
||||
UNIT_TO_ABBREVIATION = {
|
||||
"Meters": "m",
|
||||
"Feet": "ft",
|
||||
"Inches": "in",
|
||||
"Degrees": "deg",
|
||||
"Rotations": "rot",
|
||||
"Radians": "rad",
|
||||
}
|
||||
|
||||
# Make DataFrame to facilitate plotting
|
||||
if filename.suffix == ".json":
|
||||
raw_data = json.loads(filename.read_text())
|
||||
unit = raw_data["units"]
|
||||
|
||||
# Get Unit
|
||||
try:
|
||||
abbreviation = UNIT_TO_ABBREVIATION[unit]
|
||||
except KeyError:
|
||||
raise ValueError("Invalid Unit")
|
||||
|
||||
# Make Columns
|
||||
columns = ["Timestamp (s)", "Test"]
|
||||
if "Drive" in raw_data["test"]:
|
||||
columns.extend(
|
||||
[
|
||||
"Left Volts (V)",
|
||||
"Right Volts (V)",
|
||||
f"Left Position ({abbreviation})",
|
||||
f"Right Position ({abbreviation})",
|
||||
f"Left Velocity ({abbreviation}/s)",
|
||||
f"Right Velocity ({abbreviation}/s)",
|
||||
"Gyro Position (deg)",
|
||||
"Gyro Rate (deg/s)",
|
||||
]
|
||||
)
|
||||
unit_columns = columns[4:8]
|
||||
else:
|
||||
columns.extend(
|
||||
["Volts (V)", f"Position ({abbreviation})", f"Velocity ({abbreviation}/s)"]
|
||||
)
|
||||
unit_columns = columns[3:]
|
||||
|
||||
prepared_data = pd.DataFrame(columns=columns)
|
||||
for test in raw_data.keys():
|
||||
if "-" not in test:
|
||||
continue
|
||||
formatted_entry = [[pt[0], test, *pt[1:]] for pt in raw_data[test]]
|
||||
prepared_data = pd.concat(
|
||||
[prepared_data, pd.DataFrame(formatted_entry, columns=columns)]
|
||||
)
|
||||
|
||||
units_per_rot = raw_data["unitsPerRotation"]
|
||||
|
||||
for column in unit_columns:
|
||||
prepared_data[column] *= units_per_rot
|
||||
else:
|
||||
prepared_data = pd.read_csv(filename)
|
||||
|
||||
# First 2 columns are Timestamp and Test
|
||||
for column in prepared_data.columns[2:]:
|
||||
# Configure Plot Labels
|
||||
plt.figure()
|
||||
plt.xlabel("Timestamp (s)")
|
||||
plt.ylabel(column)
|
||||
|
||||
# Configure title without units
|
||||
print(column)
|
||||
end = column.find("(")
|
||||
plt.title(f"{column[:end].strip()} vs Time")
|
||||
|
||||
# Plot data for each test
|
||||
for test in pd.unique(prepared_data["Test"]):
|
||||
test_data = prepared_data[prepared_data["Test"] == test]
|
||||
plt.plot(test_data["Timestamp (s)"], test_data[column], label=test)
|
||||
plt.legend()
|
||||
|
||||
plt.show()
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <IconsFontAwesome6.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
@@ -23,6 +24,20 @@ void sysid::CreateTooltip(const char* text) {
|
||||
}
|
||||
}
|
||||
|
||||
void sysid::CreateErrorTooltip(const char* text) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
|
||||
ICON_FA_TRIANGLE_EXCLAMATION);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "%s", text);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void sysid::CreateErrorPopup(bool& isError, std::string_view errorMessage) {
|
||||
if (isError) {
|
||||
ImGui::OpenPopup("Exception Caught!");
|
||||
|
||||
@@ -173,7 +173,7 @@ AnalysisManager::AnalysisManager(TestData data, Settings& settings,
|
||||
: m_data{std::move(data)}, m_logger{logger}, m_settings{settings} {
|
||||
// Reset settings for Dynamic Test Limits
|
||||
m_settings.stepTestDuration = units::second_t{0.0};
|
||||
m_settings.motionThreshold = std::numeric_limits<double>::infinity();
|
||||
m_settings.velocityThreshold = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
|
||||
void AnalysisManager::PrepareData() {
|
||||
@@ -192,41 +192,90 @@ AnalysisManager::FeedforwardGains AnalysisManager::CalculateFeedforward() {
|
||||
|
||||
WPI_INFO(m_logger, "{}", "Calculating Gains");
|
||||
// Calculate feedforward gains from the data.
|
||||
const auto& ff = sysid::CalculateFeedforwardGains(
|
||||
GetFilteredData(), m_data.mechanismType, false);
|
||||
FeedforwardGains ffGains = {ff};
|
||||
const auto& analysisType = m_data.mechanismType;
|
||||
const auto& ff =
|
||||
sysid::CalculateFeedforwardGains(GetFilteredData(), analysisType, false);
|
||||
|
||||
const auto& Ks = ff.coeffs[0];
|
||||
const auto& Kv = ff.coeffs[1];
|
||||
const auto& Ka = ff.coeffs[2];
|
||||
|
||||
if (Ka <= 0 || Kv < 0) {
|
||||
throw InvalidDataError(
|
||||
fmt::format("The calculated feedforward gains of kS: {}, Kv: {}, Ka: "
|
||||
"{} are erroneous. Your Ka should be > 0 while your Kv and "
|
||||
"Ks constants should both >= 0. Try adjusting the "
|
||||
"filtering and trimming settings or collect better data.",
|
||||
Ks, Kv, Ka));
|
||||
FeedforwardGain KsGain = {
|
||||
.gain = Ks, .descriptor = "Voltage needed to overcome static friction."};
|
||||
if (Ks < 0) {
|
||||
KsGain.isValidGain = false;
|
||||
KsGain.errorMessage = fmt::format(
|
||||
"Calculated Ks gain of: {0:.3f} is erroneous! Ks should be >= 0.", Ks);
|
||||
}
|
||||
|
||||
return ffGains;
|
||||
const auto& Kv = ff.coeffs[1];
|
||||
FeedforwardGain KvGain = {
|
||||
.gain = Kv,
|
||||
.descriptor =
|
||||
"Voltage needed to hold/cruise at a constant velocity while "
|
||||
"overcoming the counter-electromotive force and any additional "
|
||||
"friction."};
|
||||
if (Kv < 0) {
|
||||
KvGain.isValidGain = false;
|
||||
KvGain.errorMessage = fmt::format(
|
||||
"Calculated Kv gain of: {0:.3f} is erroneous! Kv should be >= 0.", Kv);
|
||||
}
|
||||
|
||||
const auto& Ka = ff.coeffs[2];
|
||||
FeedforwardGain KaGain = {
|
||||
.gain = Ka,
|
||||
.descriptor =
|
||||
"Voltage needed to induce a given acceleration in the motor shaft."};
|
||||
if (Ka <= 0) {
|
||||
KaGain.isValidGain = false;
|
||||
KaGain.errorMessage = fmt::format(
|
||||
"Calculated Ka gain of: {0:.3f} is erroneous! Ka should be > 0.", Ka);
|
||||
}
|
||||
|
||||
if (analysisType == analysis::kSimple) {
|
||||
return FeedforwardGains{
|
||||
.olsResult = ff, .Ks = KsGain, .Kv = KvGain, .Ka = KaGain};
|
||||
}
|
||||
|
||||
if (analysisType == analysis::kElevator || analysisType == analysis::kArm) {
|
||||
const auto& Kg = ff.coeffs[3];
|
||||
FeedforwardGain KgGain = {
|
||||
Kg, "Voltage needed to counteract the force of gravity."};
|
||||
if (Kg < 0) {
|
||||
KgGain.isValidGain = false;
|
||||
KgGain.errorMessage = fmt::format(
|
||||
"Calculated Kg gain of: {0:.3f} is erroneous! Kg should be >= 0.",
|
||||
Ka);
|
||||
}
|
||||
|
||||
// Elevator analysis only requires Kg
|
||||
if (analysisType == analysis::kElevator) {
|
||||
return FeedforwardGains{.olsResult = ff,
|
||||
.Ks = KsGain,
|
||||
.Kv = KvGain,
|
||||
.Ka = KaGain,
|
||||
.Kg = KgGain};
|
||||
} else {
|
||||
// Arm analysis requires Kg and an angle offset
|
||||
FeedforwardGain offset = {
|
||||
.gain = ff.coeffs[4],
|
||||
.descriptor =
|
||||
"This is the angle offset which, when added to the angle "
|
||||
"measurement, zeroes it out when the arm is horizontal. This is "
|
||||
"needed for the arm feedforward to work."};
|
||||
return FeedforwardGains{ff, KsGain, KvGain, KaGain, KgGain, offset};
|
||||
}
|
||||
}
|
||||
|
||||
return FeedforwardGains{.olsResult = ff};
|
||||
}
|
||||
|
||||
sysid::FeedbackGains AnalysisManager::CalculateFeedback(
|
||||
std::vector<double> ff) {
|
||||
const auto& Kv = ff[1];
|
||||
const auto& Ka = ff[2];
|
||||
const FeedforwardGain& Kv, const FeedforwardGain& Ka) {
|
||||
FeedbackGains fb;
|
||||
if (m_settings.type == FeedbackControllerLoopType::kPosition) {
|
||||
fb = sysid::CalculatePositionFeedbackGains(
|
||||
m_settings.preset, m_settings.lqr, Kv, Ka,
|
||||
m_settings.convertGainsToEncTicks ? m_settings.gearing * m_settings.cpr
|
||||
: 1);
|
||||
m_settings.preset, m_settings.lqr, Kv.gain, Ka.gain);
|
||||
} else {
|
||||
fb = sysid::CalculateVelocityFeedbackGains(
|
||||
m_settings.preset, m_settings.lqr, Kv, Ka,
|
||||
m_settings.convertGainsToEncTicks ? m_settings.gearing * m_settings.cpr
|
||||
: 1);
|
||||
m_settings.preset, m_settings.lqr, Kv.gain, Ka.gain);
|
||||
}
|
||||
|
||||
return fb;
|
||||
|
||||
@@ -20,7 +20,7 @@ using Ka_t = decltype(1_V / 1_mps_sq);
|
||||
|
||||
FeedbackGains sysid::CalculatePositionFeedbackGains(
|
||||
const FeedbackControllerPreset& preset, const LQRParameters& params,
|
||||
double Kv, double Ka, double encFactor) {
|
||||
double Kv, double Ka) {
|
||||
// If acceleration requires no effort, velocity becomes an input for position
|
||||
// control. We choose an appropriate model in this case to avoid numerical
|
||||
// instabilities in the LQR.
|
||||
@@ -34,9 +34,9 @@ FeedbackGains sysid::CalculatePositionFeedbackGains(
|
||||
// Compensate for any latency from sensor measurements, filtering, etc.
|
||||
controller.LatencyCompensate(system, preset.period, 0.0_s);
|
||||
|
||||
return {controller.K(0, 0) * preset.outputConversionFactor / encFactor,
|
||||
return {controller.K(0, 0) * preset.outputConversionFactor,
|
||||
controller.K(0, 1) * preset.outputConversionFactor /
|
||||
(encFactor * (preset.normalized ? 1 : preset.period.value()))};
|
||||
(preset.normalized ? 1 : preset.period.value())};
|
||||
}
|
||||
|
||||
// This is our special model to avoid instabilities in the LQR.
|
||||
@@ -49,8 +49,7 @@ FeedbackGains sysid::CalculatePositionFeedbackGains(
|
||||
// Compensate for any latency from sensor measurements, filtering, etc.
|
||||
controller.LatencyCompensate(system, preset.period, 0.0_s);
|
||||
|
||||
return {Kv * controller.K(0, 0) * preset.outputConversionFactor / encFactor,
|
||||
0.0};
|
||||
return {Kv * controller.K(0, 0) * preset.outputConversionFactor, 0.0};
|
||||
}
|
||||
|
||||
FeedbackGains sysid::CalculateVelocityFeedbackGains(
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <frc/filter/LinearFilter.h>
|
||||
#include <frc/filter/MedianFilter.h>
|
||||
#include <units/math.h>
|
||||
#include <wpi/MathExtras.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace sysid;
|
||||
@@ -126,7 +127,7 @@ sysid::TrimStepVoltageData(std::vector<PreparedData>* data,
|
||||
auto motionBegins = std::find_if(
|
||||
data->begin(), data->end(), [settings, firstPosition](auto& datum) {
|
||||
return std::abs(datum.position - firstPosition) >
|
||||
(settings->motionThreshold * datum.dt.value());
|
||||
(settings->velocityThreshold * datum.dt.value());
|
||||
});
|
||||
|
||||
units::second_t positionDelay;
|
||||
@@ -138,7 +139,11 @@ sysid::TrimStepVoltageData(std::vector<PreparedData>* data,
|
||||
|
||||
auto maxAccel = std::max_element(
|
||||
data->begin(), data->end(), [](const auto& a, const auto& b) {
|
||||
return std::abs(a.acceleration) < std::abs(b.acceleration);
|
||||
// Since we don't know if its a forward or backwards test here, we use
|
||||
// the sign of each point's velocity to determine how to compare their
|
||||
// accelerations.
|
||||
return wpi::sgn(a.velocity) * a.acceleration <
|
||||
wpi::sgn(b.velocity) * b.acceleration;
|
||||
});
|
||||
|
||||
units::second_t velocityDelay;
|
||||
@@ -153,18 +158,22 @@ sysid::TrimStepVoltageData(std::vector<PreparedData>* data,
|
||||
|
||||
minStepTime = std::min(data->at(0).timestamp - firstTimestamp, minStepTime);
|
||||
|
||||
// Find maximum speed reached
|
||||
const auto maxSpeed =
|
||||
GetMaxSpeed(*data, [](auto&& pt) { return pt.velocity; });
|
||||
// Find place where 90% of maximum speed exceeded
|
||||
auto endIt =
|
||||
std::find_if(data->begin(), data->end(), [&](const PreparedData& entry) {
|
||||
return std::abs(entry.velocity) > maxSpeed * 0.9;
|
||||
});
|
||||
// If step test duration not yet specified, calculate default
|
||||
if (settings->stepTestDuration == 0_s) {
|
||||
// Find maximum speed reached
|
||||
const auto maxSpeed =
|
||||
GetMaxSpeed(*data, [](auto&& pt) { return pt.velocity; });
|
||||
// Find place where 90% of maximum speed exceeded
|
||||
auto endIt = std::find_if(
|
||||
data->begin(), data->end(), [&](const PreparedData& entry) {
|
||||
return std::abs(entry.velocity) > maxSpeed * 0.9;
|
||||
});
|
||||
|
||||
if (endIt != data->end()) {
|
||||
settings->stepTestDuration = std::min(
|
||||
endIt->timestamp - data->front().timestamp + minStepTime, maxStepTime);
|
||||
if (endIt != data->end()) {
|
||||
settings->stepTestDuration =
|
||||
std::min(endIt->timestamp - data->front().timestamp + minStepTime,
|
||||
maxStepTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Find first entry greater than the step test duration
|
||||
@@ -326,13 +335,13 @@ void sysid::InitialTrimAndFilter(
|
||||
maxStepTime = GetMaxStepTime(preparedData);
|
||||
|
||||
// Calculate Velocity Threshold if it hasn't been set yet
|
||||
if (settings->motionThreshold == std::numeric_limits<double>::infinity()) {
|
||||
if (settings->velocityThreshold == std::numeric_limits<double>::infinity()) {
|
||||
for (auto& it : preparedData) {
|
||||
auto key = it.first();
|
||||
auto& dataset = it.getValue();
|
||||
if (wpi::contains(key, "quasistatic")) {
|
||||
settings->motionThreshold =
|
||||
std::min(settings->motionThreshold,
|
||||
settings->velocityThreshold =
|
||||
std::min(settings->velocityThreshold,
|
||||
GetNoiseFloor(dataset, kNoiseMeanWindow,
|
||||
[](auto&& pt) { return pt.velocity; }));
|
||||
}
|
||||
@@ -344,13 +353,13 @@ void sysid::InitialTrimAndFilter(
|
||||
auto& dataset = it.getValue();
|
||||
|
||||
// Trim quasistatic test data to remove all points where voltage is zero or
|
||||
// velocity < motion threshold.
|
||||
// velocity < velocity threshold.
|
||||
if (wpi::contains(key, "quasistatic")) {
|
||||
dataset.erase(std::remove_if(dataset.begin(), dataset.end(),
|
||||
[&](const auto& pt) {
|
||||
return std::abs(pt.voltage) <= 0 ||
|
||||
std::abs(pt.velocity) <
|
||||
settings->motionThreshold;
|
||||
settings->velocityThreshold;
|
||||
}),
|
||||
dataset.end());
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ Analyzer::Analyzer(glass::Storage& storage, wpi::Logger& logger)
|
||||
void Analyzer::UpdateFeedforwardGains() {
|
||||
WPI_INFO(m_logger, "{}", "Gain calc");
|
||||
try {
|
||||
const auto& [ff] = m_manager->CalculateFeedforward();
|
||||
m_ff = ff.coeffs;
|
||||
m_accelRSquared = ff.rSquared;
|
||||
m_accelRMSE = ff.rmse;
|
||||
const auto& feedforwardGains = m_manager->CalculateFeedforward();
|
||||
m_feedforwardGains = feedforwardGains;
|
||||
m_accelRSquared = feedforwardGains.olsResult.rSquared;
|
||||
m_accelRMSE = feedforwardGains.olsResult.rmse;
|
||||
m_settings.preset.measurementDelay =
|
||||
m_settings.type == FeedbackControllerLoopType::kPosition
|
||||
? m_manager->GetPositionDelay()
|
||||
@@ -61,7 +61,7 @@ void Analyzer::UpdateFeedforwardGains() {
|
||||
m_state = AnalyzerState::kGeneralDataError;
|
||||
HandleError(e.what());
|
||||
} catch (const sysid::NoQuasistaticDataError& e) {
|
||||
m_state = AnalyzerState::kMotionThresholdError;
|
||||
m_state = AnalyzerState::kVelocityThresholdError;
|
||||
HandleError(e.what());
|
||||
} catch (const sysid::NoDynamicDataError& e) {
|
||||
m_state = AnalyzerState::kTestDurationError;
|
||||
@@ -80,16 +80,22 @@ void Analyzer::UpdateFeedforwardGains() {
|
||||
|
||||
void Analyzer::UpdateFeedbackGains() {
|
||||
WPI_INFO(m_logger, "{}", "Updating feedback gains");
|
||||
if (m_ff[1] > 0 && m_ff[2] > 0) {
|
||||
const auto& fb = m_manager->CalculateFeedback(m_ff);
|
||||
m_timescale = units::second_t{m_ff[2] / m_ff[1]};
|
||||
|
||||
const auto& Kv = m_feedforwardGains.Kv;
|
||||
const auto& Ka = m_feedforwardGains.Ka;
|
||||
if (Kv.isValidGain && Ka.isValidGain) {
|
||||
const auto& fb = m_manager->CalculateFeedback(Kv, Ka);
|
||||
m_timescale = units::second_t{Ka.gain / Kv.gain};
|
||||
m_timescaleValid = true;
|
||||
m_Kp = fb.Kp;
|
||||
m_Kd = fb.Kd;
|
||||
} else {
|
||||
m_timescaleValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Analyzer::DisplayGain(const char* text, double* data,
|
||||
bool readOnly = true) {
|
||||
bool Analyzer::DisplayDouble(const char* text, double* data,
|
||||
bool readOnly = true) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (readOnly) {
|
||||
return ImGui::InputDouble(text, data, 0.0, 0.0, "%.5G",
|
||||
@@ -106,14 +112,14 @@ static void SetPosition(double beginX, double beginY, double xShift,
|
||||
}
|
||||
|
||||
bool Analyzer::IsErrorState() {
|
||||
return m_state == AnalyzerState::kMotionThresholdError ||
|
||||
return m_state == AnalyzerState::kVelocityThresholdError ||
|
||||
m_state == AnalyzerState::kTestDurationError ||
|
||||
m_state == AnalyzerState::kGeneralDataError ||
|
||||
m_state == AnalyzerState::kFileError;
|
||||
}
|
||||
|
||||
bool Analyzer::IsDataErrorState() {
|
||||
return m_state == AnalyzerState::kMotionThresholdError ||
|
||||
return m_state == AnalyzerState::kVelocityThresholdError ||
|
||||
m_state == AnalyzerState::kTestDurationError ||
|
||||
m_state == AnalyzerState::kGeneralDataError;
|
||||
}
|
||||
@@ -121,7 +127,7 @@ bool Analyzer::IsDataErrorState() {
|
||||
void Analyzer::ResetData() {
|
||||
m_plot.ResetData();
|
||||
m_manager = std::make_unique<AnalysisManager>(m_settings, m_logger);
|
||||
m_ff = std::vector<double>{1, 1, 1};
|
||||
m_feedforwardGains = AnalysisManager::FeedforwardGains{};
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
|
||||
@@ -182,7 +188,8 @@ void Analyzer::ConfigParamsOnFileSelect() {
|
||||
// Estimate qp as 1/10 native distance unit
|
||||
m_settings.lqr.qp = 0.1;
|
||||
// Estimate qv as 1/4 * max velocity = 1/4 * (12V - kS) / kV
|
||||
m_settings.lqr.qv = 0.25 * (12.0 - m_ff[0]) / m_ff[1];
|
||||
m_settings.lqr.qv =
|
||||
0.25 * (12.0 - m_feedforwardGains.Ks.gain) / m_feedforwardGains.Kv.gain;
|
||||
}
|
||||
|
||||
void Analyzer::Display() {
|
||||
@@ -246,7 +253,7 @@ void Analyzer::Display() {
|
||||
}
|
||||
case AnalyzerState::kGeneralDataError:
|
||||
case AnalyzerState::kTestDurationError:
|
||||
case AnalyzerState::kMotionThresholdError: {
|
||||
case AnalyzerState::kVelocityThresholdError: {
|
||||
CreateErrorPopup(m_errorPopup, m_exception);
|
||||
if (DisplayResetAndUnitOverride()) {
|
||||
return;
|
||||
@@ -269,7 +276,7 @@ void Analyzer::PrepareData() {
|
||||
m_state = AnalyzerState::kGeneralDataError;
|
||||
HandleError(e.what());
|
||||
} catch (const sysid::NoQuasistaticDataError& e) {
|
||||
m_state = AnalyzerState::kMotionThresholdError;
|
||||
m_state = AnalyzerState::kVelocityThresholdError;
|
||||
HandleError(e.what());
|
||||
} catch (const sysid::NoDynamicDataError& e) {
|
||||
m_state = AnalyzerState::kTestDurationError;
|
||||
@@ -302,8 +309,9 @@ void Analyzer::PrepareGraphs() {
|
||||
AbortDataPrep();
|
||||
m_dataThread = std::thread([&] {
|
||||
m_plot.SetData(m_manager->GetRawData(), m_manager->GetFilteredData(),
|
||||
m_manager->GetUnit(), m_ff, m_manager->GetStartTimes(),
|
||||
m_manager->GetAnalysisType(), m_abortDataPrep);
|
||||
m_manager->GetUnit(), m_feedforwardGains,
|
||||
m_manager->GetStartTimes(), m_manager->GetAnalysisType(),
|
||||
m_abortDataPrep);
|
||||
});
|
||||
UpdateFeedbackGains();
|
||||
m_state = AnalyzerState::kNominalDisplay;
|
||||
@@ -355,28 +363,28 @@ void Analyzer::DisplayGraphs() {
|
||||
|
||||
// If a JSON is selected
|
||||
if (m_state == AnalyzerState::kNominalDisplay) {
|
||||
DisplayGain("Acceleration R²", &m_accelRSquared);
|
||||
DisplayDouble("Acceleration R²", &m_accelRSquared);
|
||||
CreateTooltip(
|
||||
"The coefficient of determination of the OLS fit of acceleration "
|
||||
"versus velocity and voltage. Acceleration is extremely noisy, "
|
||||
"so this is generally quite small.");
|
||||
|
||||
ImGui::SameLine();
|
||||
DisplayGain("Acceleration RMSE", &m_accelRMSE);
|
||||
DisplayDouble("Acceleration RMSE", &m_accelRMSE);
|
||||
CreateTooltip(
|
||||
"The standard deviation of the residuals from the predicted "
|
||||
"acceleration."
|
||||
"This can be interpreted loosely as the mean measured disturbance "
|
||||
"from the \"ideal\" system equation.");
|
||||
|
||||
DisplayGain("Sim velocity R²", m_plot.GetSimRSquared());
|
||||
DisplayDouble("Sim velocity R²", m_plot.GetSimRSquared());
|
||||
CreateTooltip(
|
||||
"The coefficient of determination the simulated velocity. "
|
||||
"Velocity is much less-noisy than acceleration, so this "
|
||||
"is pretty close to 1 for a decent fit.");
|
||||
|
||||
ImGui::SameLine();
|
||||
DisplayGain("Sim velocity RMSE", m_plot.GetSimRMSE());
|
||||
DisplayDouble("Sim velocity RMSE", m_plot.GetSimRMSE());
|
||||
CreateTooltip(
|
||||
"The standard deviation of the residuals from the simulated velocity "
|
||||
"predictions - essentially the size of the mean error of the "
|
||||
@@ -407,7 +415,7 @@ void Analyzer::AbortDataPrep() {
|
||||
|
||||
void Analyzer::DisplayFeedforwardParameters(float beginX, float beginY) {
|
||||
// Increase spacing to not run into trackwidth in the normal analyzer view
|
||||
constexpr double kHorizontalOffset = 0.9;
|
||||
constexpr double kHorizontalOffset = 1.1;
|
||||
SetPosition(beginX, beginY, kHorizontalOffset, 0);
|
||||
|
||||
bool displayAll =
|
||||
@@ -429,15 +437,15 @@ void Analyzer::DisplayFeedforwardParameters(float beginX, float beginY) {
|
||||
"filter's sliding window.");
|
||||
}
|
||||
|
||||
if (displayAll || m_state == AnalyzerState::kMotionThresholdError) {
|
||||
if (displayAll || m_state == AnalyzerState::kVelocityThresholdError) {
|
||||
// Wait for enter before refresh so decimal inputs like "0.2" don't
|
||||
// prematurely refresh with a velocity threshold of "0".
|
||||
SetPosition(beginX, beginY, kHorizontalOffset, 1);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
double threshold = m_settings.motionThreshold;
|
||||
double threshold = m_settings.velocityThreshold;
|
||||
if (ImGui::InputDouble("Velocity Threshold", &threshold, 0.0, 0.0, "%.3f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
m_settings.motionThreshold = std::max(0.0, threshold);
|
||||
m_settings.velocityThreshold = std::max(0.0, threshold);
|
||||
PrepareData();
|
||||
}
|
||||
CreateTooltip("Velocity data below this threshold will be ignored.");
|
||||
@@ -457,20 +465,20 @@ void Analyzer::DisplayFeedforwardParameters(float beginX, float beginY) {
|
||||
|
||||
void Analyzer::CollectFeedforwardGains(float beginX, float beginY) {
|
||||
SetPosition(beginX, beginY, 0, 0);
|
||||
if (DisplayGain("Kv", &m_ff[1], false)) {
|
||||
if (DisplayDouble("Kv", &m_feedforwardGains.Kv.gain, false)) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
|
||||
SetPosition(beginX, beginY, 0, 1);
|
||||
if (DisplayGain("Ka", &m_ff[2], false)) {
|
||||
if (DisplayDouble("Ka", &m_feedforwardGains.Ka.gain, false)) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
|
||||
SetPosition(beginX, beginY, 0, 2);
|
||||
// Show Timescale
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
DisplayGain("Response Timescale (ms)",
|
||||
reinterpret_cast<double*>(&m_timescale));
|
||||
DisplayDouble("Response Timescale (ms)",
|
||||
reinterpret_cast<double*>(&m_timescale));
|
||||
CreateTooltip(
|
||||
"The characteristic timescale of the system response in milliseconds. "
|
||||
"Both the control loop period and total signal delay should be "
|
||||
@@ -478,21 +486,39 @@ void Analyzer::CollectFeedforwardGains(float beginX, float beginY) {
|
||||
"system.");
|
||||
}
|
||||
|
||||
void Analyzer::DisplayFeedforwardGain(const char* text,
|
||||
AnalysisManager::FeedforwardGain& ffGain,
|
||||
bool readOnly = true) {
|
||||
DisplayDouble(text, &ffGain.gain, readOnly);
|
||||
if (!ffGain.isValidGain) {
|
||||
// Display invalid gain message with warning and tooltip
|
||||
CreateErrorTooltip(ffGain.errorMessage.c_str());
|
||||
}
|
||||
|
||||
// Display descriptor message as tooltip, whether the gain is valid or not
|
||||
CreateTooltip(ffGain.descriptor.c_str());
|
||||
}
|
||||
|
||||
void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) {
|
||||
SetPosition(beginX, beginY, 0, 0);
|
||||
DisplayGain("Ks", &m_ff[0]);
|
||||
DisplayFeedforwardGain("Ks", m_feedforwardGains.Ks);
|
||||
|
||||
SetPosition(beginX, beginY, 0, 1);
|
||||
DisplayGain("Kv", &m_ff[1]);
|
||||
DisplayFeedforwardGain("Kv", m_feedforwardGains.Kv);
|
||||
|
||||
SetPosition(beginX, beginY, 0, 2);
|
||||
DisplayGain("Ka", &m_ff[2]);
|
||||
DisplayFeedforwardGain("Ka", m_feedforwardGains.Ka);
|
||||
|
||||
SetPosition(beginX, beginY, 0, 3);
|
||||
// Show Timescale
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
DisplayGain("Response Timescale (ms)",
|
||||
reinterpret_cast<double*>(&m_timescale));
|
||||
DisplayDouble("Response Timescale (ms)",
|
||||
reinterpret_cast<double*>(&m_timescale));
|
||||
if (!m_timescaleValid) {
|
||||
CreateErrorTooltip(
|
||||
"Response timescale calculation invalid. Ensure that calculated gains "
|
||||
"are valid.");
|
||||
}
|
||||
CreateTooltip(
|
||||
"The characteristic timescale of the system response in milliseconds. "
|
||||
"Both the control loop period and total signal delay should be "
|
||||
@@ -501,8 +527,8 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) {
|
||||
|
||||
SetPosition(beginX, beginY, 0, 4);
|
||||
auto positionDelay = m_manager->GetPositionDelay();
|
||||
DisplayGain("Position Measurement Delay (ms)",
|
||||
reinterpret_cast<double*>(&positionDelay));
|
||||
DisplayDouble("Position Measurement Delay (ms)",
|
||||
reinterpret_cast<double*>(&positionDelay));
|
||||
CreateTooltip(
|
||||
"The average elapsed time between the first application of "
|
||||
"voltage and the first detected change in mechanism position "
|
||||
@@ -512,8 +538,8 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) {
|
||||
|
||||
SetPosition(beginX, beginY, 0, 5);
|
||||
auto velocityDelay = m_manager->GetVelocityDelay();
|
||||
DisplayGain("Velocity Measurement Delay (ms)",
|
||||
reinterpret_cast<double*>(&velocityDelay));
|
||||
DisplayDouble("Velocity Measurement Delay (ms)",
|
||||
reinterpret_cast<double*>(&velocityDelay));
|
||||
CreateTooltip(
|
||||
"The average elapsed time between the first application of "
|
||||
"voltage and the maximum calculated mechanism acceleration "
|
||||
@@ -524,20 +550,20 @@ void Analyzer::DisplayFeedforwardGains(float beginX, float beginY) {
|
||||
SetPosition(beginX, beginY, 0, 6);
|
||||
|
||||
if (m_manager->GetAnalysisType() == analysis::kElevator) {
|
||||
DisplayGain("Kg", &m_ff[3]);
|
||||
DisplayFeedforwardGain("Kg", m_feedforwardGains.Kg);
|
||||
} else if (m_manager->GetAnalysisType() == analysis::kArm) {
|
||||
DisplayGain("Kg", &m_ff[3]);
|
||||
DisplayFeedforwardGain("Kg", m_feedforwardGains.Kg);
|
||||
|
||||
double offset;
|
||||
auto unit = m_manager->GetUnit();
|
||||
if (unit == "Radians") {
|
||||
offset = m_ff[4];
|
||||
offset = m_feedforwardGains.offset.gain;
|
||||
} else if (unit == "Degrees") {
|
||||
offset = m_ff[4] / std::numbers::pi * 180.0;
|
||||
offset = m_feedforwardGains.offset.gain / std::numbers::pi * 180.0;
|
||||
} else if (unit == "Rotations") {
|
||||
offset = m_ff[4] / (2 * std::numbers::pi);
|
||||
offset = m_feedforwardGains.offset.gain / (2 * std::numbers::pi);
|
||||
}
|
||||
DisplayGain(
|
||||
DisplayDouble(
|
||||
fmt::format("Angle offset to horizontal ({})", GetAbbreviation(unit))
|
||||
.c_str(),
|
||||
&offset);
|
||||
@@ -562,7 +588,6 @@ void Analyzer::DisplayFeedbackGains() {
|
||||
m_settings.type = FeedbackControllerLoopType::kVelocity;
|
||||
m_selectedLoopType =
|
||||
static_cast<int>(FeedbackControllerLoopType::kVelocity);
|
||||
m_settings.convertGainsToEncTicks = m_selectedPreset > 2;
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@@ -646,59 +671,6 @@ void Analyzer::DisplayFeedbackGains() {
|
||||
"accurate if the characteristic timescale of the mechanism "
|
||||
"is small.");
|
||||
|
||||
// Add CPR and Gearing for converting Feedback Gains
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Checkbox("Convert Gains to Encoder Counts",
|
||||
&m_settings.convertGainsToEncTicks)) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
sysid::CreateTooltip(
|
||||
"Whether the feedback gains should be in terms of encoder counts or "
|
||||
"output units. Because smart motor controllers usually don't have "
|
||||
"direct access to the output units (i.e. m/s for a drivetrain), they "
|
||||
"perform feedback on the encoder counts directly. If you are using a "
|
||||
"PID Controller on the RoboRIO, you are probably performing feedback "
|
||||
"on the output units directly.\n\nNote that if you have properly set "
|
||||
"up position and velocity conversion factors with the SPARK MAX, you "
|
||||
"can leave this box unchecked. The motor controller will perform "
|
||||
"feedback on the output directly.");
|
||||
|
||||
if (m_settings.convertGainsToEncTicks) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputDouble("##Numerator", &m_gearingNumerator, 0.0, 0.0, "%.4f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue) &&
|
||||
m_gearingNumerator > 0) {
|
||||
m_settings.gearing = m_gearingNumerator / m_gearingDenominator;
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputDouble("##Denominator", &m_gearingDenominator, 0.0, 0.0,
|
||||
"%.4f", ImGuiInputTextFlags_EnterReturnsTrue) &&
|
||||
m_gearingDenominator > 0) {
|
||||
m_settings.gearing = m_gearingNumerator / m_gearingDenominator;
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
sysid::CreateTooltip(
|
||||
"The gearing between the encoder and the motor shaft (# of encoder "
|
||||
"turns / # of motor shaft turns).");
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5);
|
||||
if (ImGui::InputInt("CPR", &m_settings.cpr, 0, 0,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue) &&
|
||||
m_settings.cpr > 0) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
sysid::CreateTooltip(
|
||||
"The counts per rotation of your encoder. This is the number of counts "
|
||||
"reported in user code when the encoder is rotated exactly once. Some "
|
||||
"common values for various motors/encoders are:\n\n"
|
||||
"Falcon 500: 2048\nNEO: 1\nCTRE Mag Encoder / CANCoder: 4096\nREV "
|
||||
"Through Bore Encoder: 8192\n");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -725,10 +697,10 @@ void Analyzer::DisplayFeedbackGains() {
|
||||
// Show Kp and Kd.
|
||||
float beginY = ImGui::GetCursorPosY();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
DisplayGain("Kp", &m_Kp);
|
||||
DisplayDouble("Kp", &m_Kp);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
DisplayGain("Kd", &m_Kd);
|
||||
DisplayDouble("Kd", &m_Kd);
|
||||
|
||||
// Come back to the starting y pos.
|
||||
ImGui::SetCursorPosY(beginY);
|
||||
@@ -740,8 +712,8 @@ void Analyzer::DisplayFeedbackGains() {
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(ImGui::GetFontSize() * 9);
|
||||
if (DisplayGain(fmt::format("Max Position Error{}", unit).c_str(),
|
||||
&m_settings.lqr.qp, false)) {
|
||||
if (DisplayDouble(fmt::format("Max Position Error{}", unit).c_str(),
|
||||
&m_settings.lqr.qp, false)) {
|
||||
if (m_settings.lqr.qp > 0) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
@@ -754,14 +726,14 @@ void Analyzer::DisplayFeedbackGains() {
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(ImGui::GetFontSize() * 9);
|
||||
if (DisplayGain(fmt::format("Max Velocity Error{}", unit).c_str(),
|
||||
&m_settings.lqr.qv, false)) {
|
||||
if (DisplayDouble(fmt::format("Max Velocity Error{}", unit).c_str(),
|
||||
&m_settings.lqr.qv, false)) {
|
||||
if (m_settings.lqr.qv > 0) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
}
|
||||
ImGui::SetCursorPosX(ImGui::GetFontSize() * 9);
|
||||
if (DisplayGain("Max Control Effort (V)", &m_settings.lqr.r, false)) {
|
||||
if (DisplayDouble("Max Control Effort (V)", &m_settings.lqr.r, false)) {
|
||||
if (m_settings.lqr.r > 0) {
|
||||
UpdateFeedbackGains();
|
||||
}
|
||||
|
||||
@@ -127,16 +127,16 @@ void AnalyzerPlot::SetRawData(const Storage& data, std::string_view unit,
|
||||
|
||||
void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData,
|
||||
std::string_view unit,
|
||||
const std::vector<double>& ffGains,
|
||||
const AnalysisManager::FeedforwardGains& ffGains,
|
||||
const std::array<units::second_t, 4>& startTimes,
|
||||
AnalysisType type, std::atomic<bool>& abort) {
|
||||
double simSquaredErrorSum = 0;
|
||||
double squaredVariationSum = 0;
|
||||
int timeSeriesPoints = 0;
|
||||
|
||||
const auto& Ks = ffGains[0];
|
||||
const auto& Kv = ffGains[1];
|
||||
const auto& Ka = ffGains[2];
|
||||
const auto& Ks = ffGains.Ks.gain;
|
||||
const auto& Kv = ffGains.Kv.gain;
|
||||
const auto& Ka = ffGains.Ka.gain;
|
||||
|
||||
auto& [slowForward, slowBackward, fastForward, fastBackward] = filteredData;
|
||||
auto& [rawSlowForward, rawSlowBackward, rawFastForward, rawFastBackward] =
|
||||
@@ -223,7 +223,7 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData,
|
||||
|
||||
// Populate simulated time domain data
|
||||
if (type == analysis::kElevator) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
m_quasistaticData.simData = PopulateTimeDomainSim(
|
||||
rawSlow, startTimes, fastStep, sysid::ElevatorSim{Ks, Kv, Ka, Kg},
|
||||
&simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints);
|
||||
@@ -231,8 +231,8 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData,
|
||||
rawFast, startTimes, fastStep, sysid::ElevatorSim{Ks, Kv, Ka, Kg},
|
||||
&simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints);
|
||||
} else if (type == analysis::kArm) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& offset = ffGains[4];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
const auto& offset = ffGains.offset.gain;
|
||||
m_quasistaticData.simData = PopulateTimeDomainSim(
|
||||
rawSlow, startTimes, fastStep, sysid::ArmSim{Ks, Kv, Ka, Kg, offset},
|
||||
&simSquaredErrorSum, &squaredVariationSum, &timeSeriesPoints);
|
||||
@@ -288,10 +288,10 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData,
|
||||
std::copysign(Ks / Ka, slow[i].velocity);
|
||||
|
||||
if (type == analysis::kElevator) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
accelPortion -= Kg / Ka;
|
||||
} else if (type == analysis::kArm) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
accelPortion -= Kg / Ka * slow[i].cos;
|
||||
}
|
||||
|
||||
@@ -307,10 +307,10 @@ void AnalyzerPlot::SetData(const Storage& rawData, const Storage& filteredData,
|
||||
std::copysign(Ks / Ka, fast[i].velocity);
|
||||
|
||||
if (type == analysis::kElevator) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
accelPortion -= Kg / Ka;
|
||||
} else if (type == analysis::kArm) {
|
||||
const auto& Kg = ffGains[3];
|
||||
const auto& Kg = ffGains.Kg.gain;
|
||||
accelPortion -= Kg / Ka * fast[i].cos;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user