mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
936d3b9f83 | ||
|
|
6e31230adc | ||
|
|
aaf24e2552 | ||
|
|
659b37ef9d | ||
|
|
4630191fa4 | ||
|
|
948625de9d | ||
|
|
3848eb8b16 | ||
|
|
01d0e12603 | ||
|
|
a1c87e1e15 | ||
|
|
fa7240a501 | ||
|
|
ffb4d38e24 | ||
|
|
f57c188f2e | ||
|
|
8471c4fb26 | ||
|
|
c97acd18e7 | ||
|
|
ffb590bfcc | ||
|
|
10c038d9bf | ||
|
|
2d2eaa3eff | ||
|
|
4d28b1f0cd | ||
|
|
3de800a607 | ||
|
|
eff5923778 | ||
|
|
c8521a3c33 | ||
|
|
d71eb2cf39 | ||
|
|
2c98939c18 | ||
|
|
a18a7409fb | ||
|
|
2f19cf4524 | ||
|
|
c3a8bdc240 | ||
|
|
1032c9b917 | ||
|
|
2e07902d76 | ||
|
|
3e22e45066 | ||
|
|
79d1bd6c8f | ||
|
|
fe341a16f5 | ||
|
|
62abf46b3f | ||
|
|
a95a5e0d9b | ||
|
|
d6f6ceaba5 | ||
|
|
0922f8af59 | ||
|
|
6812302ff9 | ||
|
|
f3f86b8e78 | ||
|
|
1a2680b9e5 | ||
|
|
435bbb6a8c | ||
|
|
3cf44e0a53 | ||
|
|
40b367513f | ||
|
|
9f563d584a | ||
|
|
af4adf5379 | ||
|
|
2560146da3 | ||
|
|
eae3a6397a | ||
|
|
959611420b | ||
|
|
9522f2e8c7 | ||
|
|
e42a0b6cf0 | ||
|
|
d1c7032dec | ||
|
|
d241bc81ae | ||
|
|
cb7f39afa1 | ||
|
|
99b5ad9ebb | ||
|
|
c14b237757 | ||
|
|
d447c7dc32 | ||
|
|
247420c9c1 | ||
|
|
04b112e004 | ||
|
|
be0ce99007 | ||
|
|
69e8d0b65d | ||
|
|
94e685e1bd | ||
|
|
5899f3dd28 | ||
|
|
f82aa1d564 | ||
|
|
fe5c2cf4b7 | ||
|
|
43d40c6e9e | ||
|
|
3d44d8f79c | ||
|
|
ba6fe8ff2e | ||
|
|
5337258888 | ||
|
|
29bf9d6ef1 | ||
|
|
483beb6361 | ||
|
|
fdaec77594 | ||
|
|
8494a5761b | ||
|
|
45590eea22 | ||
|
|
834a64920b | ||
|
|
2c2ccb3618 | ||
|
|
fb5c8c39ae | ||
|
|
f7d39193a4 | ||
|
|
aec796b212 | ||
|
|
fb13bb2393 | ||
|
|
c517ec6779 | ||
|
|
e8cbf2a717 | ||
|
|
e9c86df468 | ||
|
|
6ba8c289c5 | ||
|
|
3f1672e89f | ||
|
|
15be5cbf1f | ||
|
|
4cf0e5e6db | ||
|
|
6b1898f12e | ||
|
|
b3426e9c0d | ||
|
|
38c1a1f3e0 | ||
|
|
4488e25f16 | ||
|
|
cfdb3058ee | ||
|
|
64adff5fea | ||
|
|
6efc58e3db | ||
|
|
f393989a5b | ||
|
|
d6ed20c1e4 | ||
|
|
7c524014c8 | ||
|
|
406d055f07 | ||
|
|
04a90b5dd1 | ||
|
|
8c5bfa0132 | ||
|
|
bc80c55353 | ||
|
|
9c3b51ca0f | ||
|
|
26584ff145 | ||
|
|
42c3d52863 | ||
|
|
64e72f7103 | ||
|
|
e955037980 | ||
|
|
fb99910c23 | ||
|
|
e620bd4d3f | ||
|
|
a44e761d9e | ||
|
|
ea1974d576 | ||
|
|
85a0bd43c2 |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.gradle text eol=lf
|
||||
*.java text eol=lf
|
||||
*.md text eol=lf
|
||||
*.xml text eol=lf
|
||||
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
publish:
|
||||
name: "Documentation - Publish"
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Set environment variables (Development)
|
||||
run: |
|
||||
echo "TARGET_FOLDER=$BASE_PATH/development" >> $GITHUB_ENV
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/main'
|
||||
- name: Set environment variables (Tag)
|
||||
run: |
|
||||
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
|
||||
12
.github/workflows/gradle.yml
vendored
12
.github/workflows/gradle.yml
vendored
@@ -38,6 +38,8 @@ jobs:
|
||||
path: build/allOutputs
|
||||
|
||||
build-host:
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -69,12 +71,12 @@ jobs:
|
||||
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- name: Set Keychain Lock Timeout
|
||||
run: security set-keychain-settings -lut 3600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
@@ -85,7 +87,7 @@ jobs:
|
||||
run: ./gradlew build -PbuildServer -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
|
||||
(github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
@@ -134,12 +136,12 @@ jobs:
|
||||
- name: Combine
|
||||
if: |
|
||||
!startsWith(github.ref, 'refs/tags/v') &&
|
||||
github.ref != 'refs/heads/master'
|
||||
github.ref != 'refs/heads/main'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib
|
||||
- name: Combine (Master)
|
||||
if: |
|
||||
github.repository_owner == 'wpilibsuite' &&
|
||||
github.ref == 'refs/heads/master'
|
||||
github.ref == 'refs/heads/main'
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
|
||||
10
.github/workflows/lint-format.yml
vendored
10
.github/workflows/lint-format.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f master origin/master
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
@@ -35,13 +35,9 @@ jobs:
|
||||
run: |
|
||||
git fetch --prune --unshallow
|
||||
git checkout -b pr
|
||||
git branch -f master origin/master
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
git branch -f main origin/main
|
||||
- name: Install clang-format and clang-tidy
|
||||
run: sudo apt-get update -q && sudo apt-get install -y clang-format-10 clang-tidy-10
|
||||
run: sudo apt-get update -q && sudo apt-get install -y clang-format-10 clang-tidy-10 python3.8
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Create compile_commands.json
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -222,5 +222,6 @@ compile_commands.json
|
||||
# clang configuration and clangd cache
|
||||
.clang
|
||||
.clangd/
|
||||
.cache/
|
||||
|
||||
imgui.ini
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2020",
|
||||
"projectYear": "2021",
|
||||
"teamNumber": 0
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Contributing to WPILib
|
||||
|
||||
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules in the [code of conduct](https://github.com/wpilibsuite/allwpilib/blob/master/CODE_OF_CONDUCT.md), and behave with Gracious Professionalism.
|
||||
So you want to contribute your changes back to WPILib. Great! We have a few contributing rules that will help you make sure your changes will be accepted into the project. Please remember to follow the rules in the [code of conduct](https://github.com/wpilibsuite/allwpilib/blob/main/CODE_OF_CONDUCT.md), and behave with Gracious Professionalism.
|
||||
|
||||
- [General Contribution Rules](#general-contribution-rules)
|
||||
- [What to Contribute](#what-to-contribute)
|
||||
@@ -45,7 +45,7 @@ While the library should be fully formatted according to the styles, additional
|
||||
|
||||
### Pull Request Format
|
||||
|
||||
Changes should be submitted as a Pull Request against the master branch of WPILib. For most changes, commits will be squashed upon merge. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. We may ask you to break a PR into multiple standalone PRs or commits for rebase within one PR to separate unrelated changes. No change will be merged unless it is up to date with the current master. We do this to make sure that the git history isn't too cluttered.
|
||||
Changes should be submitted as a Pull Request against the main branch of WPILib. For most changes, commits will be squashed upon merge. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. We may ask you to break a PR into multiple standalone PRs or commits for rebase within one PR to separate unrelated changes. No change will be merged unless it is up to date with the current main branch. We do this to make sure that the git history isn't too cluttered.
|
||||
|
||||
### Merge Process
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ For Java dependencies, there is likely a file related to the specific dependency
|
||||
Note, changing artifact locations (This includes changing the artifact year currently, I have an issue open to change this) requires updating the `native-utils` plugin
|
||||
|
||||
## Publishing allwpilib
|
||||
allwpilib publishes to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
allwpilib publishes to the development repo on every push to main. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
|
||||
## Publishing desktop tools
|
||||
Desktop tools publish to the development repo on every push to master. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
Desktop tools publish to the development repo on every push to main. To publish a release build, upload a new tag, and a release will automatically be built and published.
|
||||
|
||||
## Publishing VS Code
|
||||
Before publishing, make sure to update the gradlerio version in `vscode-wpilib/resources/gradle/version.txt` Also make sure the gradle wrapper version matches the wrapper required by gradlerio.
|
||||
|
||||
@@ -9,7 +9,7 @@ We provide two repositories. These repositories are:
|
||||
* (Development) https://frcmaven.wpi.edu/artifactory/development/
|
||||
|
||||
The release repository is where official WPILib releases are pushed.
|
||||
The development repository is where development releases of every commit to [master](https://github.com/wpilibsuite/allwpilib/tree/master) is pushed.
|
||||
The development repository is where development releases of every commit to [main](https://github.com/wpilibsuite/allwpilib/tree/main) is pushed.
|
||||
|
||||
## Artifact classifiers
|
||||
We provide two base types of artifacts.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# WPILib Project
|
||||
|
||||

|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/cpp/)
|
||||
[](https://first.wpi.edu/wpilib/allwpilib/docs/development/java/)
|
||||
|
||||
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
|
||||
|
||||
@@ -49,7 +51,7 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
|
||||
Clone the WPILib repository and follow the instructions above for installing any required tooling.
|
||||
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/master/README.md) for wpiformat setup instructions.
|
||||
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2021.1.0"
|
||||
implementation "edu.wpi.first:native-utils:2021.1.1"
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ if (WITH_JAVA)
|
||||
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
|
||||
endif()
|
||||
|
||||
find_file(OPENCV_JAR_FILE NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin NO_DEFAULT_PATH)
|
||||
find_file(OPENCV_JAR_FILE NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/share/java NO_DEFAULT_PATH)
|
||||
find_file(OPENCV_JNI_FILE NAMES libopencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.so
|
||||
libopencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dylib
|
||||
opencv_java${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dll
|
||||
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/bin/Release ${OpenCV_INSTALL_PATH}/bin/Debug ${OpenCV_INSTALL_PATH}/lib NO_DEFAULT_PATH)
|
||||
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/bin/Release ${OpenCV_INSTALL_PATH}/bin/Debug ${OpenCV_INSTALL_PATH}/lib ${OpenCV_INSTALL_PATH}/lib/jni NO_DEFAULT_PATH)
|
||||
|
||||
file(GLOB
|
||||
cscore_jni_src src/main/native/cpp/jni/CameraServerJNI.cpp)
|
||||
|
||||
@@ -313,6 +313,19 @@ public class CameraServerJNI {
|
||||
|
||||
public static native void removeListener(int handle);
|
||||
|
||||
public static native int createListenerPoller();
|
||||
|
||||
public static native void destroyListenerPoller(int poller);
|
||||
|
||||
public static native int addPolledListener(int poller, int eventMask, boolean immediateNotify);
|
||||
|
||||
public static native VideoEvent[] pollListener(int poller) throws InterruptedException;
|
||||
|
||||
public static native VideoEvent[] pollListenerTimeout(int poller, double timeout)
|
||||
throws InterruptedException;
|
||||
|
||||
public static native void cancelPollListener(int poller);
|
||||
|
||||
//
|
||||
// Telemetry Functions
|
||||
//
|
||||
|
||||
@@ -26,7 +26,8 @@ public class VideoEvent {
|
||||
kTelemetryUpdated(0x8000),
|
||||
kSinkPropertyCreated(0x10000),
|
||||
kSinkPropertyValueUpdated(0x20000),
|
||||
kSinkPropertyChoicesUpdated(0x40000);
|
||||
kSinkPropertyChoicesUpdated(0x40000),
|
||||
kUsbCamerasChanged(0x80000);
|
||||
|
||||
private final int value;
|
||||
|
||||
@@ -84,6 +85,8 @@ public class VideoEvent {
|
||||
return Kind.kSinkPropertyValueUpdated;
|
||||
case 0x40000:
|
||||
return Kind.kSinkPropertyChoicesUpdated;
|
||||
case 0x80000:
|
||||
return Kind.kUsbCamerasChanged;
|
||||
default:
|
||||
return Kind.kUnknown;
|
||||
}
|
||||
@@ -102,7 +105,8 @@ public class VideoEvent {
|
||||
int property,
|
||||
int propertyKind,
|
||||
int value,
|
||||
String valueStr) {
|
||||
String valueStr,
|
||||
int listener) {
|
||||
this.kind = getKindFromInt(kind);
|
||||
this.sourceHandle = source;
|
||||
this.sinkHandle = sink;
|
||||
@@ -112,6 +116,7 @@ public class VideoEvent {
|
||||
this.propertyKind = VideoProperty.getKindFromInt(propertyKind);
|
||||
this.value = value;
|
||||
this.valueStr = valueStr;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MemberName")
|
||||
@@ -145,6 +150,10 @@ public class VideoEvent {
|
||||
@SuppressWarnings("MemberName")
|
||||
public String valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
@SuppressWarnings("MemberName")
|
||||
public int listener;
|
||||
|
||||
public VideoSource getSource() {
|
||||
return new VideoSource(CameraServerJNI.copySource(sourceHandle));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
package edu.wpi.cscore;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -20,15 +24,31 @@ public class VideoListener implements AutoCloseable {
|
||||
* of events for the current library state.
|
||||
*/
|
||||
public VideoListener(Consumer<VideoEvent> listener, int eventMask, boolean immediateNotify) {
|
||||
m_handle = CameraServerJNI.addListener(listener, eventMask, immediateNotify);
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_poller == 0) {
|
||||
s_poller = CameraServerJNI.createListenerPoller();
|
||||
startThread();
|
||||
}
|
||||
m_handle = CameraServerJNI.addPolledListener(s_poller, eventMask, immediateNotify);
|
||||
s_listeners.put(m_handle, listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (m_handle != 0) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
s_listeners.remove(m_handle);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
CameraServerJNI.removeListener(m_handle);
|
||||
m_handle = 0;
|
||||
}
|
||||
m_handle = 0;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
@@ -36,4 +56,71 @@ public class VideoListener implements AutoCloseable {
|
||||
}
|
||||
|
||||
private int m_handle;
|
||||
|
||||
private static final ReentrantLock s_lock = new ReentrantLock();
|
||||
private static final Map<Integer, Consumer<VideoEvent>> s_listeners = new HashMap<>();
|
||||
private static Thread s_thread;
|
||||
private static int s_poller;
|
||||
private static boolean s_waitQueue;
|
||||
private static final Condition s_waitQueueCond = s_lock.newCondition();
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
private static void startThread() {
|
||||
s_thread =
|
||||
new Thread(
|
||||
() -> {
|
||||
boolean wasInterrupted = false;
|
||||
while (!Thread.interrupted()) {
|
||||
VideoEvent[] events;
|
||||
try {
|
||||
events = CameraServerJNI.pollListener(s_poller);
|
||||
} catch (InterruptedException ex) {
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (s_waitQueue) {
|
||||
s_waitQueue = false;
|
||||
s_waitQueueCond.signalAll();
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
// don't try to destroy poller, as its handle is likely no longer valid
|
||||
wasInterrupted = true;
|
||||
break;
|
||||
}
|
||||
for (VideoEvent event : events) {
|
||||
Consumer<VideoEvent> listener;
|
||||
s_lock.lock();
|
||||
try {
|
||||
listener = s_listeners.get(event.listener);
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} catch (Throwable throwable) {
|
||||
System.err.println(
|
||||
"Unhandled exception during listener callback: " + throwable.toString());
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s_lock.lock();
|
||||
try {
|
||||
if (!wasInterrupted) {
|
||||
CameraServerJNI.destroyListenerPoller(s_poller);
|
||||
}
|
||||
s_poller = 0;
|
||||
} finally {
|
||||
s_lock.unlock();
|
||||
}
|
||||
},
|
||||
"VideoListener");
|
||||
s_thread.setDaemon(true);
|
||||
s_thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ class Handle {
|
||||
kSource,
|
||||
kSink,
|
||||
kListener,
|
||||
kSinkProperty
|
||||
kSinkProperty,
|
||||
kListenerPoller
|
||||
};
|
||||
enum { kIndexMax = 0xffff };
|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ static void def_log_func(unsigned int level, const char* file,
|
||||
wpi::errs() << oss.str();
|
||||
}
|
||||
|
||||
Instance::Instance() : telemetry(notifier), networkListener(logger, notifier) {
|
||||
Instance::Instance()
|
||||
: telemetry(notifier),
|
||||
networkListener(logger, notifier),
|
||||
usbCameraListener(logger, notifier) {
|
||||
SetDefaultLogger();
|
||||
}
|
||||
|
||||
@@ -52,6 +55,7 @@ void Instance::Shutdown() {
|
||||
m_sinks.FreeAll();
|
||||
m_sources.FreeAll();
|
||||
networkListener.Stop();
|
||||
usbCameraListener.Stop();
|
||||
telemetry.Stop();
|
||||
notifier.Stop();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "SourceImpl.h"
|
||||
#include "Telemetry.h"
|
||||
#include "UnlimitedHandleResource.h"
|
||||
#include "UsbCameraListener.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
@@ -54,6 +55,7 @@ class Instance {
|
||||
Notifier notifier;
|
||||
Telemetry telemetry;
|
||||
NetworkListener networkListener;
|
||||
UsbCameraListener usbCameraListener;
|
||||
|
||||
private:
|
||||
UnlimitedHandleResource<Handle, SourceData, Handle::kSource> m_sources;
|
||||
|
||||
@@ -15,170 +15,26 @@
|
||||
|
||||
using namespace cs;
|
||||
|
||||
bool Notifier::s_destroyed = false;
|
||||
Notifier::Notifier() {}
|
||||
|
||||
namespace {
|
||||
// Vector which provides an integrated freelist for removal and reuse of
|
||||
// individual elements.
|
||||
template <typename T>
|
||||
class UidVector {
|
||||
public:
|
||||
using size_type = typename std::vector<T>::size_type;
|
||||
|
||||
size_type size() const { return m_vector.size(); }
|
||||
T& operator[](size_type i) { return m_vector[i]; }
|
||||
const T& operator[](size_type i) const { return m_vector[i]; }
|
||||
|
||||
// Add a new T to the vector. If there are elements on the freelist,
|
||||
// reuses the last one; otherwise adds to the end of the vector.
|
||||
// Returns the resulting element index (+1).
|
||||
template <class... Args>
|
||||
unsigned int emplace_back(Args&&... args) {
|
||||
unsigned int uid;
|
||||
if (m_free.empty()) {
|
||||
uid = m_vector.size();
|
||||
m_vector.emplace_back(std::forward<Args>(args)...);
|
||||
} else {
|
||||
uid = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_vector[uid] = T(std::forward<Args>(args)...);
|
||||
}
|
||||
return uid + 1;
|
||||
}
|
||||
|
||||
// Removes the identified element by replacing it with a default-constructed
|
||||
// one. The element is added to the freelist for later reuse.
|
||||
void erase(unsigned int uid) {
|
||||
--uid;
|
||||
if (uid >= m_vector.size() || !m_vector[uid]) {
|
||||
return;
|
||||
}
|
||||
m_free.push_back(uid);
|
||||
m_vector[uid] = T();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_vector;
|
||||
std::vector<unsigned int> m_free;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class Notifier::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: m_on_start(std::move(on_start)), m_on_exit(std::move(on_exit)) {}
|
||||
|
||||
void Main() override;
|
||||
|
||||
struct Listener {
|
||||
Listener() = default;
|
||||
Listener(std::function<void(const RawEvent& event)> callback_,
|
||||
int eventMask_)
|
||||
: callback(std::move(callback_)), eventMask(eventMask_) {}
|
||||
|
||||
explicit operator bool() const { return static_cast<bool>(callback); }
|
||||
|
||||
std::string prefix;
|
||||
std::function<void(const RawEvent& event)> callback;
|
||||
int eventMask;
|
||||
};
|
||||
UidVector<Listener> m_listeners;
|
||||
|
||||
std::queue<RawEvent> m_notifications;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
};
|
||||
|
||||
Notifier::Notifier() {
|
||||
s_destroyed = false;
|
||||
}
|
||||
|
||||
Notifier::~Notifier() {
|
||||
s_destroyed = true;
|
||||
}
|
||||
Notifier::~Notifier() {}
|
||||
|
||||
void Notifier::Start() {
|
||||
m_owner.Start(m_on_start, m_on_exit);
|
||||
DoStart();
|
||||
}
|
||||
|
||||
void Notifier::Stop() {
|
||||
m_owner.Stop();
|
||||
unsigned int Notifier::Add(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask) {
|
||||
return DoAdd(callback, eventMask);
|
||||
}
|
||||
|
||||
void Notifier::Thread::Main() {
|
||||
if (m_on_start) {
|
||||
m_on_start();
|
||||
}
|
||||
|
||||
std::unique_lock lock(m_mutex);
|
||||
while (m_active) {
|
||||
while (m_notifications.empty()) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
while (!m_notifications.empty()) {
|
||||
if (!m_active) {
|
||||
goto done;
|
||||
}
|
||||
auto item = std::move(m_notifications.front());
|
||||
m_notifications.pop();
|
||||
|
||||
// Use index because iterator might get invalidated.
|
||||
for (size_t i = 0; i < m_listeners.size(); ++i) {
|
||||
if (!m_listeners[i]) {
|
||||
continue; // removed
|
||||
}
|
||||
|
||||
// Event type must be within requested set for this listener.
|
||||
if ((item.kind & m_listeners[i].eventMask) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make a copy of the callback so we can safely release the mutex
|
||||
auto callback = m_listeners[i].callback;
|
||||
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
callback(item);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (m_on_exit) {
|
||||
m_on_exit();
|
||||
}
|
||||
}
|
||||
|
||||
int Notifier::AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask) {
|
||||
Start();
|
||||
auto thr = m_owner.GetThread();
|
||||
return thr->m_listeners.emplace_back(callback, eventMask);
|
||||
}
|
||||
|
||||
void Notifier::RemoveListener(int uid) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
thr->m_listeners.erase(uid);
|
||||
unsigned int Notifier::AddPolled(unsigned int pollerUid, int eventMask) {
|
||||
return DoAdd(pollerUid, eventMask);
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const wpi::Twine& name, CS_Source source,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
thr->m_notifications.emplace(name, source, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, name, source, static_cast<RawEvent::Kind>(kind));
|
||||
}
|
||||
|
||||
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
|
||||
@@ -188,44 +44,24 @@ void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
|
||||
|
||||
void Notifier::NotifySourceVideoMode(const SourceImpl& source,
|
||||
const VideoMode& mode) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, source.GetName(), handleData.first, mode);
|
||||
}
|
||||
|
||||
void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName,
|
||||
int property, CS_PropertyKind propertyKind,
|
||||
int value, const wpi::Twine& valueStr) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, propertyName, handleData.first,
|
||||
static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const wpi::Twine& name, CS_Sink sink,
|
||||
CS_EventKind kind) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
thr->m_notifications.emplace(name, sink, static_cast<RawEvent::Kind>(kind));
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, name, sink, static_cast<RawEvent::Kind>(kind));
|
||||
}
|
||||
|
||||
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
|
||||
@@ -235,52 +71,30 @@ void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
|
||||
|
||||
void Notifier::NotifySinkSourceChanged(const wpi::Twine& name, CS_Sink sink,
|
||||
CS_Source source) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RawEvent event{name, sink, RawEvent::kSinkSourceChanged};
|
||||
event.sourceHandle = source;
|
||||
|
||||
thr->m_notifications.emplace(std::move(event));
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, std::move(event));
|
||||
}
|
||||
|
||||
void Notifier::NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind,
|
||||
const wpi::Twine& propertyName, int property,
|
||||
CS_PropertyKind propertyKind, int value,
|
||||
const wpi::Twine& valueStr) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto handleData = Instance::GetInstance().FindSink(sink);
|
||||
|
||||
thr->m_notifications.emplace(
|
||||
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kSinkProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, propertyName, handleData.first,
|
||||
static_cast<RawEvent::Kind>(kind),
|
||||
Handle{handleData.first, property, Handle::kSinkProperty}, propertyKind,
|
||||
value, valueStr);
|
||||
}
|
||||
|
||||
void Notifier::NotifyNetworkInterfacesChanged() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged);
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, RawEvent::kNetworkInterfacesChanged);
|
||||
}
|
||||
|
||||
void Notifier::NotifyTelemetryUpdated() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) {
|
||||
return;
|
||||
}
|
||||
|
||||
thr->m_notifications.emplace(RawEvent::kTelemetryUpdated);
|
||||
thr->m_cond.notify_one();
|
||||
Send(UINT_MAX, RawEvent::kTelemetryUpdated);
|
||||
}
|
||||
|
||||
void Notifier::NotifyUsbCamerasChanged() {
|
||||
Send(UINT_MAX, RawEvent::kUsbCamerasChanged);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#define CSCORE_NOTIFIER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/SafeThread.h>
|
||||
#include <wpi/CallbackManager.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -16,7 +18,40 @@ namespace cs {
|
||||
class SinkImpl;
|
||||
class SourceImpl;
|
||||
|
||||
class Notifier {
|
||||
namespace impl {
|
||||
|
||||
struct ListenerData : public wpi::CallbackListenerData<
|
||||
std::function<void(const RawEvent& event)>> {
|
||||
ListenerData() = default;
|
||||
ListenerData(std::function<void(const RawEvent& event)> callback_,
|
||||
int eventMask_)
|
||||
: CallbackListenerData(std::move(callback_)), eventMask(eventMask_) {}
|
||||
ListenerData(unsigned int pollerUid_, int eventMask_)
|
||||
: CallbackListenerData(pollerUid_), eventMask(eventMask_) {}
|
||||
|
||||
int eventMask;
|
||||
};
|
||||
|
||||
class NotifierThread
|
||||
: public wpi::CallbackThread<NotifierThread, RawEvent, ListenerData> {
|
||||
public:
|
||||
bool Matches(const ListenerData& /*listener*/, const RawEvent& /*data*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetListener(RawEvent* data, unsigned int listener_uid) {
|
||||
data->listener = Handle(listener_uid, Handle::kListener);
|
||||
}
|
||||
|
||||
void DoCallback(std::function<void(const RawEvent& event)> callback,
|
||||
const RawEvent& data) {
|
||||
callback(data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
class Notifier : public wpi::CallbackManager<Notifier, impl::NotifierThread> {
|
||||
friend class NotifierTest;
|
||||
|
||||
public:
|
||||
@@ -24,16 +59,10 @@ class Notifier {
|
||||
~Notifier();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
static bool destroyed() { return s_destroyed; }
|
||||
|
||||
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
|
||||
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
|
||||
|
||||
int AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask);
|
||||
void RemoveListener(int uid);
|
||||
unsigned int Add(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask);
|
||||
unsigned int AddPolled(unsigned int pollerUid, int eventMask);
|
||||
|
||||
// Notification events
|
||||
void NotifySource(const wpi::Twine& name, CS_Source source,
|
||||
@@ -54,14 +83,7 @@ class Notifier {
|
||||
const wpi::Twine& valueStr);
|
||||
void NotifyNetworkInterfacesChanged();
|
||||
void NotifyTelemetryUpdated();
|
||||
|
||||
private:
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
static bool s_destroyed;
|
||||
void NotifyUsbCamerasChanged();
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
31
cscore/src/main/native/cpp/UsbCameraListener.h
Normal file
31
cscore/src/main/native/cpp/UsbCameraListener.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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.
|
||||
|
||||
#ifndef CSCORE_USBCAMERALISTENER_H_
|
||||
#define CSCORE_USBCAMERALISTENER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wpi/Logger.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
class Notifier;
|
||||
|
||||
class UsbCameraListener {
|
||||
public:
|
||||
UsbCameraListener(wpi::Logger& logger, Notifier& notifier);
|
||||
~UsbCameraListener();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_USBCAMERALISTENER_H_
|
||||
@@ -15,6 +15,39 @@
|
||||
#include "cscore_cpp.h"
|
||||
#include "cscore_raw.h"
|
||||
|
||||
static CS_Event ConvertToC(const cs::RawEvent& rawEvent) {
|
||||
CS_Event event;
|
||||
event.kind = static_cast<CS_EventKind>(static_cast<int>(rawEvent.kind));
|
||||
event.source = rawEvent.sourceHandle;
|
||||
event.sink = rawEvent.sinkHandle;
|
||||
event.name = rawEvent.name.c_str();
|
||||
event.mode = rawEvent.mode;
|
||||
event.property = rawEvent.propertyHandle;
|
||||
event.propertyKind = rawEvent.propertyKind;
|
||||
event.value = rawEvent.value;
|
||||
event.valueStr = rawEvent.valueStr.c_str();
|
||||
event.listener = rawEvent.listener;
|
||||
return event;
|
||||
}
|
||||
|
||||
template <typename O, typename I>
|
||||
static O* ConvertToC(std::vector<I>&& in, int* count) {
|
||||
using T = std::vector<I>;
|
||||
size_t size = in.size();
|
||||
O* out = static_cast<O*>(wpi::safe_malloc(size * sizeof(O) + sizeof(T)));
|
||||
*count = size;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
out[i] = ConvertToC(in[i]);
|
||||
}
|
||||
|
||||
// retain vector at end of returned array
|
||||
alignas(T) unsigned char buf[sizeof(T)];
|
||||
new (buf) T(std::move(in));
|
||||
std::memcpy(out + size * sizeof(O), buf, sizeof(T));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
CS_PropertyKind CS_GetPropertyKind(CS_Property property, CS_Status* status) {
|
||||
@@ -332,16 +365,7 @@ CS_Listener CS_AddListener(void* data,
|
||||
CS_Status* status) {
|
||||
return cs::AddListener(
|
||||
[=](const cs::RawEvent& rawEvent) {
|
||||
CS_Event event;
|
||||
event.kind = static_cast<CS_EventKind>(static_cast<int>(rawEvent.kind));
|
||||
event.source = rawEvent.sourceHandle;
|
||||
event.sink = rawEvent.sinkHandle;
|
||||
event.name = rawEvent.name.c_str();
|
||||
event.mode = rawEvent.mode;
|
||||
event.property = rawEvent.propertyHandle;
|
||||
event.propertyKind = rawEvent.propertyKind;
|
||||
event.value = rawEvent.value;
|
||||
event.valueStr = rawEvent.valueStr.c_str();
|
||||
CS_Event event = ConvertToC(rawEvent);
|
||||
callback(data, &event);
|
||||
},
|
||||
eventMask, immediateNotify, status);
|
||||
@@ -351,6 +375,45 @@ void CS_RemoveListener(CS_Listener handle, CS_Status* status) {
|
||||
return cs::RemoveListener(handle, status);
|
||||
}
|
||||
|
||||
CS_ListenerPoller CS_CreateListenerPoller(void) {
|
||||
return cs::CreateListenerPoller();
|
||||
}
|
||||
|
||||
void CS_DestroyListenerPoller(CS_ListenerPoller poller) {
|
||||
cs::DestroyListenerPoller(poller);
|
||||
}
|
||||
|
||||
CS_Listener CS_AddPolledListener(CS_ListenerPoller poller, int eventMask,
|
||||
CS_Bool immediateNotify, CS_Status* status) {
|
||||
return cs::AddPolledListener(poller, eventMask, immediateNotify, status);
|
||||
}
|
||||
|
||||
struct CS_Event* CS_PollListener(CS_ListenerPoller poller, int* count) {
|
||||
return ConvertToC<CS_Event>(cs::PollListener(poller), count);
|
||||
}
|
||||
|
||||
struct CS_Event* CS_PollListenerTimeout(CS_ListenerPoller poller, int* count,
|
||||
double timeout, CS_Bool* timedOut) {
|
||||
bool cppTimedOut = false;
|
||||
auto arrCpp = cs::PollListener(poller, timeout, &cppTimedOut);
|
||||
*timedOut = cppTimedOut;
|
||||
return ConvertToC<CS_Event>(std::move(arrCpp), count);
|
||||
}
|
||||
|
||||
void CS_CancelPollListener(CS_ListenerPoller poller) {
|
||||
cs::CancelPollListener(poller);
|
||||
}
|
||||
|
||||
void CS_FreeEvents(CS_Event* arr, int count) {
|
||||
// destroy vector saved at end of array
|
||||
using T = std::vector<cs::RawEvent>;
|
||||
alignas(T) unsigned char buf[sizeof(T)];
|
||||
std::memcpy(buf, arr + count * sizeof(CS_Event), sizeof(T));
|
||||
reinterpret_cast<T*>(buf)->~T();
|
||||
|
||||
std::free(arr);
|
||||
}
|
||||
|
||||
int CS_NotifierDestroyed(void) {
|
||||
return cs::NotifierDestroyed();
|
||||
}
|
||||
|
||||
@@ -705,19 +705,12 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) {
|
||||
// Listener Functions
|
||||
//
|
||||
|
||||
void SetListenerOnStart(std::function<void()> onStart) {
|
||||
Instance::GetInstance().notifier.SetOnStart(onStart);
|
||||
}
|
||||
void SetListenerOnStart(std::function<void()> onStart) {}
|
||||
|
||||
void SetListenerOnExit(std::function<void()> onExit) {
|
||||
Instance::GetInstance().notifier.SetOnExit(onExit);
|
||||
}
|
||||
void SetListenerOnExit(std::function<void()> onExit) {}
|
||||
|
||||
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask, bool immediateNotify,
|
||||
CS_Status* status) {
|
||||
static void StartBackground(int eventMask, bool immediateNotify) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
int uid = inst.notifier.AddListener(callback, eventMask);
|
||||
if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) {
|
||||
// start network interface event listener
|
||||
inst.networkListener.Start();
|
||||
@@ -725,6 +718,21 @@ CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
inst.notifier.NotifyNetworkInterfacesChanged();
|
||||
}
|
||||
}
|
||||
if ((eventMask & CS_USB_CAMERAS_CHANGED) != 0) {
|
||||
// start network interface event listener
|
||||
inst.usbCameraListener.Start();
|
||||
if (immediateNotify) {
|
||||
inst.notifier.NotifyUsbCamerasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask, bool immediateNotify,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
int uid = inst.notifier.Add(callback, eventMask);
|
||||
StartBackground(eventMask, immediateNotify);
|
||||
if (immediateNotify) {
|
||||
// TODO
|
||||
}
|
||||
@@ -737,11 +745,67 @@ void RemoveListener(CS_Listener handle, CS_Status* status) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return;
|
||||
}
|
||||
Instance::GetInstance().notifier.RemoveListener(uid);
|
||||
Instance::GetInstance().notifier.Remove(uid);
|
||||
}
|
||||
|
||||
CS_ListenerPoller CreateListenerPoller() {
|
||||
auto& inst = Instance::GetInstance();
|
||||
return Handle(inst.notifier.CreatePoller(), Handle::kListenerPoller);
|
||||
}
|
||||
|
||||
void DestroyListenerPoller(CS_ListenerPoller poller) {
|
||||
int uid = Handle{poller}.GetTypedIndex(Handle::kListenerPoller);
|
||||
if (uid < 0) {
|
||||
return;
|
||||
}
|
||||
Instance::GetInstance().notifier.RemovePoller(uid);
|
||||
}
|
||||
|
||||
CS_Listener AddPolledListener(CS_ListenerPoller poller, int eventMask,
|
||||
bool immediateNotify, CS_Status* status) {
|
||||
Handle handle{poller};
|
||||
int id = handle.GetTypedIndex(Handle::kListenerPoller);
|
||||
if (id < 0) {
|
||||
*status = CS_INVALID_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& inst = Instance::GetInstance();
|
||||
int uid = inst.notifier.AddPolled(id, eventMask);
|
||||
StartBackground(eventMask, immediateNotify);
|
||||
return Handle{uid, Handle::kListener};
|
||||
}
|
||||
|
||||
std::vector<RawEvent> PollListener(CS_ListenerPoller poller) {
|
||||
Handle handle{poller};
|
||||
int id = handle.GetTypedIndex(Handle::kListenerPoller);
|
||||
if (id < 0) {
|
||||
return {};
|
||||
}
|
||||
return Instance::GetInstance().notifier.Poll(id);
|
||||
}
|
||||
|
||||
std::vector<RawEvent> PollListener(CS_ListenerPoller poller, double timeout,
|
||||
bool* timedOut) {
|
||||
Handle handle{poller};
|
||||
int id = handle.GetTypedIndex(Handle::kListenerPoller);
|
||||
if (id < 0) {
|
||||
return {};
|
||||
}
|
||||
return Instance::GetInstance().notifier.Poll(id, timeout, timedOut);
|
||||
}
|
||||
|
||||
void CancelPollListener(CS_ListenerPoller poller) {
|
||||
Handle handle{poller};
|
||||
int id = handle.GetTypedIndex(Handle::kListenerPoller);
|
||||
if (id < 0) {
|
||||
return;
|
||||
}
|
||||
return Instance::GetInstance().notifier.CancelPoll(id);
|
||||
}
|
||||
|
||||
bool NotifierDestroyed() {
|
||||
return Notifier::destroyed();
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -31,6 +31,7 @@ static JClass videoModeCls;
|
||||
static JClass videoEventCls;
|
||||
static JClass rawFrameCls;
|
||||
static JException videoEx;
|
||||
static JException interruptedEx;
|
||||
static JException nullPointerEx;
|
||||
static JException unsupportedEx;
|
||||
static JException exceptionEx;
|
||||
@@ -45,6 +46,7 @@ static const JClassInit classes[] = {
|
||||
|
||||
static const JExceptionInit exceptions[] = {
|
||||
{"edu/wpi/cscore/VideoException", &videoEx},
|
||||
{"java/lang/InterruptedException", &interruptedEx},
|
||||
{"java/lang/NullPointerException", &nullPointerEx},
|
||||
{"java/lang/UnsupportedOperationException", &unsupportedEx},
|
||||
{"java/lang/Exception", &exceptionEx}};
|
||||
@@ -268,7 +270,7 @@ static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
|
||||
static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
|
||||
static jmethodID constructor =
|
||||
env->GetMethodID(videoEventCls, "<init>",
|
||||
"(IIILjava/lang/String;IIIIIIILjava/lang/String;)V");
|
||||
"(IIILjava/lang/String;IIIIIIILjava/lang/String;I)V");
|
||||
JLocal<jstring> name(env, MakeJString(env, event.name));
|
||||
JLocal<jstring> valueStr(env, MakeJString(env, event.valueStr));
|
||||
// clang-format off
|
||||
@@ -286,10 +288,23 @@ static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
|
||||
static_cast<jint>(event.propertyHandle),
|
||||
static_cast<jint>(event.propertyKind),
|
||||
static_cast<jint>(event.value),
|
||||
valueStr.obj());
|
||||
valueStr.obj(),
|
||||
static_cast<jint>(event.listener));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env, wpi::ArrayRef<cs::RawEvent> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), videoEventCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < arr.size(); ++i) {
|
||||
JLocal<jobject> elem{env, MakeJObject(env, arr[i])};
|
||||
env->SetObjectArrayElement(jarr, i, elem.obj());
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
@@ -1881,6 +1896,92 @@ Java_edu_wpi_cscore_CameraServerJNI_removeListener
|
||||
CheckStatus(env, status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: createListenerPoller
|
||||
* Signature: ()I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_createListenerPoller
|
||||
(JNIEnv*, jclass)
|
||||
{
|
||||
return cs::CreateListenerPoller();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: destroyListenerPoller
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_destroyListenerPoller
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
cs::DestroyListenerPoller(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: addPolledListener
|
||||
* Signature: (IIZ)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_addPolledListener
|
||||
(JNIEnv* env, jclass, jint poller, jint eventMask, jboolean immediateNotify)
|
||||
{
|
||||
CS_Status status = 0;
|
||||
auto rv = cs::AddPolledListener(poller, eventMask, immediateNotify, &status);
|
||||
CheckStatus(env, status);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: pollListener
|
||||
* Signature: (I)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_pollListener
|
||||
(JNIEnv* env, jclass, jint poller)
|
||||
{
|
||||
auto events = cs::PollListener(poller);
|
||||
if (events.empty()) {
|
||||
interruptedEx.Throw(env, "PollListener interrupted");
|
||||
return nullptr;
|
||||
}
|
||||
return MakeJObject(env, events);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: pollListenerTimeout
|
||||
* Signature: (ID)[Ljava/lang/Object;
|
||||
*/
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_pollListenerTimeout
|
||||
(JNIEnv* env, jclass, jint poller, jdouble timeout)
|
||||
{
|
||||
bool timed_out = false;
|
||||
auto events = cs::PollListener(poller, timeout, &timed_out);
|
||||
if (events.empty() && !timed_out) {
|
||||
interruptedEx.Throw(env, "PollListener interrupted");
|
||||
return nullptr;
|
||||
}
|
||||
return MakeJObject(env, events);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: cancelPollListener
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_cscore_CameraServerJNI_cancelPollListener
|
||||
(JNIEnv*, jclass, jint poller)
|
||||
{
|
||||
cs::CancelPollListener(poller);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_cscore_CameraServerJNI
|
||||
* Method: setTelemetryPeriod
|
||||
|
||||
@@ -46,6 +46,7 @@ typedef int CS_Status;
|
||||
typedef int CS_Handle;
|
||||
typedef CS_Handle CS_Property;
|
||||
typedef CS_Handle CS_Listener;
|
||||
typedef CS_Handle CS_ListenerPoller;
|
||||
typedef CS_Handle CS_Sink;
|
||||
typedef CS_Handle CS_Source;
|
||||
/** @} */
|
||||
@@ -169,7 +170,8 @@ enum CS_EventKind {
|
||||
CS_TELEMETRY_UPDATED = 0x8000,
|
||||
CS_SINK_PROPERTY_CREATED = 0x10000,
|
||||
CS_SINK_PROPERTY_VALUE_UPDATED = 0x20000,
|
||||
CS_SINK_PROPERTY_CHOICES_UPDATED = 0x40000
|
||||
CS_SINK_PROPERTY_CHOICES_UPDATED = 0x40000,
|
||||
CS_USB_CAMERAS_CHANGED = 0x80000
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -222,6 +224,9 @@ struct CS_Event {
|
||||
enum CS_PropertyKind propertyKind;
|
||||
int value;
|
||||
const char* valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
CS_Listener listener;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -429,9 +434,19 @@ void CS_SetListenerOnStart(void (*onStart)(void* data), void* data);
|
||||
void CS_SetListenerOnExit(void (*onExit)(void* data), void* data);
|
||||
CS_Listener CS_AddListener(
|
||||
void* data, void (*callback)(void* data, const struct CS_Event* event),
|
||||
int eventMask, int immediateNotify, CS_Status* status);
|
||||
int eventMask, CS_Bool immediateNotify, CS_Status* status);
|
||||
|
||||
void CS_RemoveListener(CS_Listener handle, CS_Status* status);
|
||||
|
||||
CS_ListenerPoller CS_CreateListenerPoller(void);
|
||||
void CS_DestroyListenerPoller(CS_ListenerPoller poller);
|
||||
CS_Listener CS_AddPolledListener(CS_ListenerPoller poller, int eventMask,
|
||||
CS_Bool immediateNotify, CS_Status* status);
|
||||
struct CS_Event* CS_PollListener(CS_ListenerPoller poller, int* count);
|
||||
struct CS_Event* CS_PollListenerTimeout(CS_ListenerPoller poller, int* count,
|
||||
double timeout, CS_Bool* timedOut);
|
||||
void CS_FreeEvents(struct CS_Event* arr, int count);
|
||||
void CS_CancelPollListener(CS_ListenerPoller poller);
|
||||
/** @} */
|
||||
|
||||
int CS_NotifierDestroyed(void);
|
||||
|
||||
@@ -116,7 +116,8 @@ struct RawEvent {
|
||||
kTelemetryUpdated = CS_TELEMETRY_UPDATED,
|
||||
kSinkPropertyCreated = CS_SINK_PROPERTY_CREATED,
|
||||
kSinkPropertyValueUpdated = CS_SINK_PROPERTY_VALUE_UPDATED,
|
||||
kSinkPropertyChoicesUpdated = CS_SINK_PROPERTY_CHOICES_UPDATED
|
||||
kSinkPropertyChoicesUpdated = CS_SINK_PROPERTY_CHOICES_UPDATED,
|
||||
kUsbCamerasChanged = CS_USB_CAMERAS_CHANGED
|
||||
};
|
||||
|
||||
RawEvent() = default;
|
||||
@@ -163,6 +164,9 @@ struct RawEvent {
|
||||
CS_PropertyKind propertyKind;
|
||||
int value;
|
||||
std::string valueStr;
|
||||
|
||||
// Listener that was triggered
|
||||
CS_Listener listener{0};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -378,6 +382,15 @@ CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
|
||||
int eventMask, bool immediateNotify, CS_Status* status);
|
||||
|
||||
void RemoveListener(CS_Listener handle, CS_Status* status);
|
||||
|
||||
CS_ListenerPoller CreateListenerPoller();
|
||||
void DestroyListenerPoller(CS_ListenerPoller poller);
|
||||
CS_Listener AddPolledListener(CS_ListenerPoller poller, int eventMask,
|
||||
bool immediateNotify, CS_Status* status);
|
||||
std::vector<RawEvent> PollListener(CS_ListenerPoller poller);
|
||||
std::vector<RawEvent> PollListener(CS_ListenerPoller poller, double timeout,
|
||||
bool* timedOut);
|
||||
void CancelPollListener(CS_ListenerPoller poller);
|
||||
/** @} */
|
||||
|
||||
bool NotifierDestroyed();
|
||||
|
||||
55
cscore/src/main/native/linux/UsbCameraListener.cpp
Normal file
55
cscore/src/main/native/linux/UsbCameraListener.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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 "UsbCameraListener.h"
|
||||
|
||||
#include <wpi/EventLoopRunner.h>
|
||||
#include <wpi/uv/FsEvent.h>
|
||||
#include <wpi/uv/Timer.h>
|
||||
|
||||
#include "Notifier.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class UsbCameraListener::Impl {
|
||||
public:
|
||||
explicit Impl(Notifier& notifier) : m_notifier(notifier) {}
|
||||
|
||||
Notifier& m_notifier;
|
||||
|
||||
std::unique_ptr<wpi::EventLoopRunner> m_runner;
|
||||
};
|
||||
|
||||
UsbCameraListener::UsbCameraListener(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_impl(std::make_unique<Impl>(notifier)) {}
|
||||
|
||||
UsbCameraListener::~UsbCameraListener() = default;
|
||||
|
||||
void UsbCameraListener::Start() {
|
||||
if (!m_impl->m_runner) {
|
||||
m_impl->m_runner = std::make_unique<wpi::EventLoopRunner>();
|
||||
m_impl->m_runner->ExecAsync([impl = m_impl.get()](wpi::uv::Loop& loop) {
|
||||
auto refreshTimer = wpi::uv::Timer::Create(loop);
|
||||
refreshTimer->timeout.connect([notifier = &impl->m_notifier] {
|
||||
notifier->NotifyUsbCamerasChanged();
|
||||
});
|
||||
refreshTimer->Unreference();
|
||||
|
||||
auto devEvents = wpi::uv::FsEvent::Create(loop);
|
||||
devEvents->fsEvent.connect([refreshTimer](const char* fn, int flags) {
|
||||
if (wpi::StringRef(fn).startswith("video")) {
|
||||
refreshTimer->Start(wpi::uv::Timer::Time(200));
|
||||
}
|
||||
});
|
||||
devEvents->Start("/dev");
|
||||
devEvents->Unreference();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void UsbCameraListener::Stop() {
|
||||
if (m_impl->m_runner) {
|
||||
m_impl->m_runner.reset();
|
||||
}
|
||||
}
|
||||
17
cscore/src/main/native/osx/UsbCameraListener.cpp
Normal file
17
cscore/src/main/native/osx/UsbCameraListener.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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 "UsbCameraListener.h"
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class UsbCameraListener::Impl {};
|
||||
|
||||
UsbCameraListener::UsbCameraListener(wpi::Logger& logger, Notifier& notifier) {}
|
||||
|
||||
UsbCameraListener::~UsbCameraListener() = default;
|
||||
|
||||
void UsbCameraListener::Start() {}
|
||||
|
||||
void UsbCameraListener::Stop() {}
|
||||
72
cscore/src/main/native/windows/UsbCameraListener.cpp
Normal file
72
cscore/src/main/native/windows/UsbCameraListener.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 "UsbCameraListener.h"
|
||||
|
||||
#include "Notifier.h"
|
||||
#include "WindowsMessagePump.h"
|
||||
|
||||
#include <dbt.h> // NOLINT(build/include_order)
|
||||
|
||||
#define IDT_TIMER1 1001
|
||||
|
||||
using namespace cs;
|
||||
|
||||
class UsbCameraListener::Impl {
|
||||
public:
|
||||
explicit Impl(Notifier& notifier) : m_notifier{notifier} {}
|
||||
|
||||
void Start() {
|
||||
m_messagePump = std::make_unique<WindowsMessagePump>(
|
||||
[this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
|
||||
return this->PumpMain(hwnd, uiMsg, wParam, lParam);
|
||||
});
|
||||
}
|
||||
|
||||
void Stop() { m_messagePump = nullptr; }
|
||||
|
||||
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (uiMsg) {
|
||||
case WM_CLOSE:
|
||||
KillTimer(hwnd, IDT_TIMER1);
|
||||
break;
|
||||
case WM_TIMER:
|
||||
if (wParam == IDT_TIMER1) {
|
||||
KillTimer(hwnd, IDT_TIMER1);
|
||||
m_notifier.NotifyUsbCamerasChanged();
|
||||
}
|
||||
break;
|
||||
case WM_DEVICECHANGE:
|
||||
PDEV_BROADCAST_HDR parameter =
|
||||
reinterpret_cast<PDEV_BROADCAST_HDR>(lParam);
|
||||
if (wParam == DBT_DEVICEARRIVAL &&
|
||||
parameter->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
|
||||
SetTimer(hwnd, IDT_TIMER1, 200, nullptr);
|
||||
} else if (wParam == DBT_DEVICEREMOVECOMPLETE &&
|
||||
parameter->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
|
||||
SetTimer(hwnd, IDT_TIMER1, 200, nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Notifier& m_notifier;
|
||||
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
|
||||
};
|
||||
|
||||
UsbCameraListener::UsbCameraListener(wpi::Logger& logger, Notifier& notifier)
|
||||
: m_impl{std::make_unique<Impl>(notifier)} {}
|
||||
|
||||
UsbCameraListener::~UsbCameraListener() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void UsbCameraListener::Start() {
|
||||
m_impl->Start();
|
||||
}
|
||||
|
||||
void UsbCameraListener::Stop() {
|
||||
m_impl->Stop();
|
||||
}
|
||||
@@ -25,6 +25,7 @@ def zipBaseNameJava = '_GROUP_edu_wpi_first_wpilibj_ID_documentation_CLS'
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
def cppProjectZips = []
|
||||
def cppIncludeRoots = []
|
||||
|
||||
cppProjectZips.add(project(':hal').cppHeadersZip)
|
||||
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
|
||||
@@ -49,6 +50,9 @@ doxygen {
|
||||
cppProjectZips.each {
|
||||
dependsOn it
|
||||
source it.source
|
||||
it.ext.includeDirs.each {
|
||||
cppIncludeRoots.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
exclude 'Eigen/**'
|
||||
@@ -57,7 +61,6 @@ doxygen {
|
||||
exclude 'uv.h'
|
||||
exclude 'uv/**'
|
||||
|
||||
|
||||
extension_mapping 'inc=C++'
|
||||
project_name 'WPILibC++'
|
||||
project_number wpilibVersioning.version.get()
|
||||
@@ -75,6 +78,9 @@ doxygen {
|
||||
html_timestamp true
|
||||
generate_treeview true
|
||||
extract_static true
|
||||
full_path_names true
|
||||
strip_from_inc_path cppIncludeRoots as String[]
|
||||
strip_from_path cppIncludeRoots as String[]
|
||||
}
|
||||
|
||||
tasks.register("zipCppDocs", Zip) {
|
||||
|
||||
@@ -21,6 +21,7 @@ repoRootNameOverride {
|
||||
includeOtherLibs {
|
||||
^GLFW
|
||||
^cscore
|
||||
^frc/
|
||||
^imgui
|
||||
^ntcore
|
||||
^wpi/
|
||||
|
||||
@@ -4,9 +4,65 @@ def baseArtifactId = 'Glass'
|
||||
def artifactGroupId = 'edu.wpi.first.tools'
|
||||
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_Glass_CLS'
|
||||
|
||||
def libBaseArtifactId = 'libglass'
|
||||
def libArtifactGroupId = 'edu.wpi.first.glass'
|
||||
def libZipBaseName = '_GROUP_edu_wpi_first_glass_ID_libglass_CLS'
|
||||
|
||||
def libntBaseArtifactId = 'libglassnt'
|
||||
def libntArtifactGroupId = 'edu.wpi.first.glass'
|
||||
def libntZipBaseName = '_GROUP_edu_wpi_first_glass_ID_libglassnt_CLS'
|
||||
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
|
||||
task libCppSourcesZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = libZipBaseName
|
||||
classifier = "sources"
|
||||
|
||||
from(licenseFile) { into '/' }
|
||||
from('src/lib/native/cpp') { into '/' }
|
||||
}
|
||||
|
||||
task libCppHeadersZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = libZipBaseName
|
||||
classifier = "headers"
|
||||
|
||||
from(licenseFile) { into '/' }
|
||||
from('src/lib/native/include') { into '/' }
|
||||
}
|
||||
|
||||
task libntCppSourcesZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = libntZipBaseName
|
||||
classifier = "sources"
|
||||
|
||||
from(licenseFile) { into '/' }
|
||||
from('src/libnt/native/cpp') { into '/' }
|
||||
}
|
||||
|
||||
task libntCppHeadersZip(type: Zip) {
|
||||
destinationDirectory = outputsFolder
|
||||
archiveBaseName = libntZipBaseName
|
||||
classifier = "headers"
|
||||
|
||||
from(licenseFile) { into '/' }
|
||||
from('src/libnt/native/include') { into '/' }
|
||||
}
|
||||
|
||||
build.dependsOn libCppHeadersZip
|
||||
build.dependsOn libCppSourcesZip
|
||||
build.dependsOn libntCppHeadersZip
|
||||
build.dependsOn libntCppSourcesZip
|
||||
|
||||
addTaskToCopyAllOutputs(libCppHeadersZip)
|
||||
addTaskToCopyAllOutputs(libCppSourcesZip)
|
||||
addTaskToCopyAllOutputs(libntCppHeadersZip)
|
||||
addTaskToCopyAllOutputs(libntCppSourcesZip)
|
||||
|
||||
model {
|
||||
publishing {
|
||||
def tasks = []
|
||||
def glassAppTaskList = []
|
||||
$.components.each { component ->
|
||||
component.binaries.each { binary ->
|
||||
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("glassApp")) {
|
||||
@@ -50,10 +106,9 @@ model {
|
||||
}
|
||||
|
||||
// Create the ZIP.
|
||||
def outputsFolder = file("$project.buildDir/outputs")
|
||||
def task = project.tasks.create("copyGlassExecutable", Zip) {
|
||||
description("Copies the Glass executable to the outputs directory.")
|
||||
destinationDir(outputsFolder)
|
||||
destinationDirectory = outputsFolder
|
||||
|
||||
archiveBaseName = '_M_' + zipBaseName
|
||||
duplicatesStrategy = 'exclude'
|
||||
@@ -73,7 +128,7 @@ model {
|
||||
}
|
||||
|
||||
task.dependsOn binary.tasks.link
|
||||
tasks.add(task)
|
||||
glassAppTaskList.add(task)
|
||||
project.build.dependsOn task
|
||||
project.artifacts { task }
|
||||
addTaskToCopyAllOutputs(task)
|
||||
@@ -82,13 +137,37 @@ model {
|
||||
}
|
||||
}
|
||||
|
||||
def libGlassTaskList = createComponentZipTasks($.components, ['glass'], libZipBaseName, Zip, project, includeStandardZipFormat)
|
||||
def libGlassntTaskList = createComponentZipTasks($.components, ['glassnt'], libntZipBaseName, Zip, project, includeStandardZipFormat)
|
||||
|
||||
publications {
|
||||
cpp(MavenPublication) {
|
||||
tasks.each { artifact it }
|
||||
glassApp(MavenPublication) {
|
||||
glassAppTaskList.each { artifact it }
|
||||
|
||||
artifactId = baseArtifactId
|
||||
groupId = artifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
libglass(MavenPublication) {
|
||||
libGlassTaskList.each { artifact it }
|
||||
|
||||
artifact libCppHeadersZip
|
||||
artifact libCppSourcesZip
|
||||
|
||||
artifactId = libBaseArtifactId
|
||||
groupId = libArtifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
libglassnt(MavenPublication) {
|
||||
libGlassntTaskList.each { artifact it }
|
||||
|
||||
artifact libntCppHeadersZip
|
||||
artifact libntCppSourcesZip
|
||||
|
||||
artifactId = libntBaseArtifactId
|
||||
groupId = libntArtifactGroupId
|
||||
version wpilibVersioning.version.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,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 "NetworkTablesSettings.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
|
||||
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
|
||||
const char* storageName)
|
||||
: m_inst{inst} {
|
||||
auto& storage = glass::GetStorage(storageName);
|
||||
m_pMode = storage.GetIntRef("mode");
|
||||
m_pIniName = storage.GetStringRef("iniName", "networktables.ini");
|
||||
m_pServerTeam = storage.GetStringRef("serverTeam");
|
||||
m_pListenAddress = storage.GetStringRef("listenAddress");
|
||||
}
|
||||
|
||||
void NetworkTablesSettings::Update() {
|
||||
if (!m_restart) {
|
||||
return;
|
||||
}
|
||||
m_restart = false;
|
||||
nt::StopClient(m_inst);
|
||||
nt::StopServer(m_inst);
|
||||
nt::StopLocal(m_inst);
|
||||
if (*m_pMode == 1) {
|
||||
wpi::StringRef serverTeam{*m_pServerTeam};
|
||||
unsigned int team;
|
||||
if (!serverTeam.contains('.') && !serverTeam.getAsInteger(10, team)) {
|
||||
nt::StartClientTeam(m_inst, team, NT_DEFAULT_PORT);
|
||||
} else {
|
||||
wpi::SmallVector<wpi::StringRef, 4> serverNames;
|
||||
wpi::SmallVector<std::pair<wpi::StringRef, unsigned int>, 4> servers;
|
||||
serverTeam.split(serverNames, ',', -1, false);
|
||||
for (auto&& serverName : serverNames) {
|
||||
servers.emplace_back(serverName, NT_DEFAULT_PORT);
|
||||
}
|
||||
nt::StartClient(m_inst, servers);
|
||||
}
|
||||
} else if (*m_pMode == 2) {
|
||||
nt::StartServer(m_inst, m_pIniName->c_str(), m_pListenAddress->c_str(),
|
||||
NT_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTablesSettings::Display() {
|
||||
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
|
||||
ImGui::Combo("Mode", m_pMode, modeOptions, 3);
|
||||
switch (*m_pMode) {
|
||||
case 1:
|
||||
ImGui::InputText("Team/IP", m_pServerTeam);
|
||||
break;
|
||||
case 2:
|
||||
ImGui::InputText("Listen Address", m_pListenAddress);
|
||||
ImGui::InputText("ini Filename", m_pIniName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ImGui::Button("Apply")) {
|
||||
m_restart = true;
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "NetworkTablesSettings.h"
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
#include "glass/networktables/NetworkTables.h"
|
||||
#include "glass/networktables/NetworkTablesProvider.h"
|
||||
#include "glass/networktables/NetworkTablesSettings.h"
|
||||
#include "glass/other/Log.h"
|
||||
#include "glass/other/Plot.h"
|
||||
|
||||
@@ -37,7 +37,7 @@ static std::unique_ptr<glass::PlotProvider> gPlotProvider;
|
||||
static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
|
||||
|
||||
static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
|
||||
static std::unique_ptr<NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static glass::LogData gNetworkTablesLog;
|
||||
static glass::Window* gNetworkTablesWindow;
|
||||
static glass::Window* gNetworkTablesSettingsWindow;
|
||||
@@ -111,7 +111,7 @@ static void NtInitialize() {
|
||||
}
|
||||
|
||||
// NetworkTables settings window
|
||||
gNetworkTablesSettings = std::make_unique<NetworkTablesSettings>();
|
||||
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>();
|
||||
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
|
||||
|
||||
gNetworkTablesSettingsWindow = gNtProvider->AddWindow(
|
||||
|
||||
@@ -50,7 +50,8 @@ void Window::Display() {
|
||||
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###%s",
|
||||
m_name.empty() ? m_id.c_str() : m_name.c_str(), m_id.c_str());
|
||||
m_name.empty() ? m_defaultName.c_str() : m_name.c_str(),
|
||||
m_id.c_str());
|
||||
|
||||
if (Begin(label, &m_visible, m_flags)) {
|
||||
if (m_renamePopupEnabled) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ void LogData::Append(const wpi::Twine& msg) {
|
||||
}
|
||||
|
||||
size_t oldSize = m_buf.size();
|
||||
wpi::raw_vector_ostream os{m_buf};
|
||||
wpi::raw_string_ostream os{m_buf};
|
||||
msg.print(os);
|
||||
for (size_t newSize = m_buf.size(); oldSize < newSize; ++oldSize) {
|
||||
if (m_buf[oldSize] == '\n') {
|
||||
@@ -32,6 +32,10 @@ void LogData::Append(const wpi::Twine& msg) {
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& LogData::GetBuffer() {
|
||||
return m_buf;
|
||||
}
|
||||
|
||||
void glass::DisplayLog(LogData* data, bool autoScroll) {
|
||||
if (data->m_buf.empty()) {
|
||||
return;
|
||||
@@ -65,6 +69,11 @@ void LogView::Display() {
|
||||
if (ImGui::Selectable("Clear")) {
|
||||
m_data->Clear();
|
||||
}
|
||||
const auto& buf = m_data->GetBuffer();
|
||||
if (ImGui::Selectable("Copy to Clipboard", false,
|
||||
buf.empty() ? ImGuiSelectableFlags_Disabled : 0)) {
|
||||
ImGui::SetClipboardText(buf.c_str());
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
|
||||
@@ -113,16 +113,26 @@ class Plot {
|
||||
|
||||
std::vector<std::unique_ptr<PlotSeries>> m_series;
|
||||
|
||||
// Returns base height; does not include actual plot height if auto-sized.
|
||||
int GetAutoBaseHeight(bool* isAuto, size_t i);
|
||||
|
||||
void SetAutoHeight(int height) {
|
||||
if (m_autoHeight) {
|
||||
m_height = height;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void EmitSettingsLimits(int axis);
|
||||
|
||||
std::string m_name;
|
||||
bool m_visible = true;
|
||||
bool m_showPause = true;
|
||||
unsigned int m_plotFlags = ImPlotFlags_Default;
|
||||
unsigned int m_plotFlags = ImPlotFlags_None;
|
||||
bool m_lockPrevX = false;
|
||||
bool m_paused = false;
|
||||
float m_viewTime = 10;
|
||||
bool m_autoHeight = true;
|
||||
int m_height = 300;
|
||||
struct PlotRange {
|
||||
double min = 0;
|
||||
@@ -131,6 +141,7 @@ class Plot {
|
||||
bool lockMax = false;
|
||||
bool apply = false;
|
||||
};
|
||||
std::string m_axisLabel[3];
|
||||
PlotRange m_axisRange[3];
|
||||
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
|
||||
};
|
||||
@@ -163,6 +174,7 @@ PlotSeries::PlotSeries(wpi::StringRef id) : m_id(id) {
|
||||
|
||||
PlotSeries::PlotSeries(DataSource* source, int yAxis) : m_yAxis(yAxis) {
|
||||
SetSource(source);
|
||||
m_id = source->GetId();
|
||||
}
|
||||
|
||||
void PlotSeries::CheckSource() {
|
||||
@@ -180,7 +192,6 @@ void PlotSeries::CheckSource() {
|
||||
|
||||
void PlotSeries::SetSource(DataSource* source) {
|
||||
m_source = source;
|
||||
m_id = source->GetId();
|
||||
|
||||
// add initial value
|
||||
m_data[m_size++] = ImPlotPoint{wpi::Now() * 1.0e-6, source->GetValue()};
|
||||
@@ -348,13 +359,13 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
|
||||
if (IsDigital()) {
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
|
||||
ImPlot::PlotDigital(label, getter, &getterData, size + 1);
|
||||
ImPlot::PlotDigitalG(label, getter, &getterData, size + 1);
|
||||
ImPlot::PopStyleVar();
|
||||
ImPlot::PopStyleVar();
|
||||
} else {
|
||||
ImPlot::SetPlotYAxis(m_yAxis);
|
||||
ImPlot::SetNextMarkerStyle(m_marker - 1);
|
||||
ImPlot::PlotLine(label, getter, &getterData, size + 1);
|
||||
ImPlot::PlotLineG(label, getter, &getterData, size + 1);
|
||||
}
|
||||
|
||||
// DND source for PlotSeries
|
||||
@@ -363,9 +374,15 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
|
||||
ImPlot::EndLegendDragDropSource();
|
||||
}
|
||||
|
||||
// Show full source name tooltip
|
||||
if (!m_name.empty() && ImPlot::IsLegendEntryHovered(label)) {
|
||||
ImGui::SetTooltip("%s", m_id.c_str());
|
||||
}
|
||||
|
||||
// Edit settings via popup
|
||||
Action rv = kNone;
|
||||
if (ImPlot::BeginLegendPopup(label)) {
|
||||
ImGui::TextUnformatted(m_id.c_str());
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
@@ -492,9 +509,9 @@ bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
return true;
|
||||
}
|
||||
if (num == 0) {
|
||||
m_plotFlags &= ~ImPlotFlags_Legend;
|
||||
m_plotFlags |= ImPlotFlags_NoLegend;
|
||||
} else {
|
||||
m_plotFlags |= ImPlotFlags_Legend;
|
||||
m_plotFlags &= ~ImPlotFlags_NoLegend;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "yaxis2") {
|
||||
@@ -526,6 +543,13 @@ bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
}
|
||||
m_viewTime = num / 1000.0;
|
||||
return true;
|
||||
} else if (name == "autoHeight") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) {
|
||||
return true;
|
||||
}
|
||||
m_autoHeight = num != 0;
|
||||
return true;
|
||||
} else if (name == "height") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) {
|
||||
@@ -570,6 +594,9 @@ bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
}
|
||||
m_axisRange[yAxis].lockMax = num != 0;
|
||||
return true;
|
||||
} else if (yName == "label") {
|
||||
m_axisLabel[yAxis] = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -578,18 +605,20 @@ bool Plot::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
void Plot::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf(
|
||||
"name=%s\nvisible=%d\nshowPause=%d\nlockPrevX=%d\nlegend=%d\n"
|
||||
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nheight=%d\n",
|
||||
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nautoHeight=%d\nheight=%d\n",
|
||||
m_name.c_str(), m_visible ? 1 : 0, m_showPause ? 1 : 0,
|
||||
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_Legend) ? 1 : 0,
|
||||
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_NoLegend) ? 0 : 1,
|
||||
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
|
||||
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
|
||||
static_cast<int>(m_viewTime * 1000), m_height);
|
||||
static_cast<int>(m_viewTime * 1000), m_autoHeight ? 1 : 0, m_height);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
out->appendf("y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n", i,
|
||||
static_cast<int>(m_axisRange[i].min * 1000), i,
|
||||
static_cast<int>(m_axisRange[i].max * 1000), i,
|
||||
m_axisRange[i].lockMin ? 1 : 0, i,
|
||||
m_axisRange[i].lockMax ? 1 : 0);
|
||||
out->appendf(
|
||||
"y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n"
|
||||
"y%d_label=%s\n",
|
||||
i, static_cast<int>(m_axisRange[i].min * 1000), i,
|
||||
static_cast<int>(m_axisRange[i].max * 1000), i,
|
||||
m_axisRange[i].lockMin ? 1 : 0, i, m_axisRange[i].lockMax ? 1 : 0, i,
|
||||
m_axisLabel[i].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,9 +687,9 @@ void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
|
||||
(paused || m_paused) ? ImGuiCond_Once : ImGuiCond_Always);
|
||||
}
|
||||
|
||||
ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_Default,
|
||||
ImPlotAxisFlags_Auxiliary,
|
||||
ImPlotAxisFlags_Auxiliary};
|
||||
ImPlotAxisFlags yFlags[3] = {ImPlotAxisFlags_None,
|
||||
ImPlotAxisFlags_NoGridLines,
|
||||
ImPlotAxisFlags_NoGridLines};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ImPlot::SetNextPlotLimitsY(
|
||||
m_axisRange[i].min, m_axisRange[i].max,
|
||||
@@ -674,9 +703,13 @@ void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ImPlot::BeginPlot(label, nullptr, nullptr, ImVec2(-1, m_height),
|
||||
m_plotFlags, ImPlotAxisFlags_Default, yFlags[0],
|
||||
yFlags[1], yFlags[2])) {
|
||||
if (ImPlot::BeginPlot(
|
||||
label, nullptr,
|
||||
m_axisLabel[0].empty() ? nullptr : m_axisLabel[0].c_str(),
|
||||
ImVec2(-1, m_height), m_plotFlags, ImPlotAxisFlags_None, yFlags[0],
|
||||
yFlags[1], yFlags[2],
|
||||
m_axisLabel[1].empty() ? nullptr : m_axisLabel[1].c_str(),
|
||||
m_axisLabel[2].empty() ? nullptr : m_axisLabel[2].c_str())) {
|
||||
for (size_t j = 0; j < m_series.size(); ++j) {
|
||||
ImGui::PushID(j);
|
||||
switch (m_series[j]->EmitPlot(view, now, j, i)) {
|
||||
@@ -708,6 +741,8 @@ void Plot::EmitSettingsLimits(int axis) {
|
||||
ImGui::Indent();
|
||||
ImGui::PushID(axis);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10);
|
||||
ImGui::InputText("Label", &m_axisLabel[axis]);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
|
||||
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
|
||||
ImGui::SameLine();
|
||||
@@ -733,7 +768,7 @@ void Plot::EmitSettings(size_t i) {
|
||||
ImGui::InputText("##editname", &m_name);
|
||||
ImGui::Checkbox("Visible", &m_visible);
|
||||
ImGui::Checkbox("Show Pause Button", &m_showPause);
|
||||
ImGui::CheckboxFlags("Show Legend", &m_plotFlags, ImPlotFlags_Legend);
|
||||
ImGui::CheckboxFlags("Hide Legend", &m_plotFlags, ImPlotFlags_NoLegend);
|
||||
if (i != 0) {
|
||||
ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
|
||||
}
|
||||
@@ -749,14 +784,34 @@ void Plot::EmitSettings(size_t i) {
|
||||
}
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::InputFloat("View Time (s)", &m_viewTime, 0.1f, 1.0f, "%.1f");
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
if (ImGui::InputInt("Height", &m_height, 10)) {
|
||||
if (m_height < 0) {
|
||||
m_height = 0;
|
||||
ImGui::Checkbox("Auto Height", &m_autoHeight);
|
||||
if (!m_autoHeight) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
if (ImGui::InputInt("Height", &m_height, 10)) {
|
||||
if (m_height < 0) {
|
||||
m_height = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Plot::GetAutoBaseHeight(bool* isAuto, size_t i) {
|
||||
*isAuto = m_autoHeight;
|
||||
|
||||
if (!m_visible) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int height = m_autoHeight ? 0 : m_height;
|
||||
|
||||
// Pause button
|
||||
if ((i == 0 || !m_lockPrevX) && m_showPause) {
|
||||
height += ImGui::GetFrameHeightWithSpacing();
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
void PlotView::Display() {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::Button("Add plot")) {
|
||||
@@ -832,6 +887,27 @@ void PlotView::Display() {
|
||||
auto ref = static_cast<const PlotSeriesRef*>(payload->Data);
|
||||
MovePlot(ref->view, ref->plotIndex, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-size plots. This requires two passes: the first pass to get the
|
||||
// total height, the second to actually set the height after averaging it
|
||||
// across all auto-sized heights.
|
||||
int availHeight = ImGui::GetContentRegionAvail().y;
|
||||
int numAuto = 0;
|
||||
for (size_t i = 0; i < m_plots.size(); ++i) {
|
||||
bool isAuto;
|
||||
availHeight -= m_plots[i]->GetAutoBaseHeight(&isAuto, i);
|
||||
availHeight -= ImGui::GetStyle().ItemSpacing.y;
|
||||
if (isAuto) {
|
||||
++numAuto;
|
||||
}
|
||||
}
|
||||
if (numAuto > 0) {
|
||||
availHeight /= numAuto;
|
||||
for (size_t i = 0; i < m_plots.size(); ++i) {
|
||||
m_plots[i]->SetAutoHeight(availHeight);
|
||||
}
|
||||
}
|
||||
|
||||
double now = wpi::Now() * 1.0e-6;
|
||||
@@ -913,10 +989,25 @@ void PlotProvider::DisplayMenu() {
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("New Plot Window")) {
|
||||
// this is an inefficient algorithm, but the number of windows is small
|
||||
char id[32];
|
||||
std::snprintf(id, sizeof(id), "Plot <%d>",
|
||||
static_cast<int>(m_windows.size()));
|
||||
AddWindow(id, std::make_unique<PlotView>(this));
|
||||
size_t numWindows = m_windows.size();
|
||||
for (size_t i = 0; i <= numWindows; ++i) {
|
||||
std::snprintf(id, sizeof(id), "Plot <%d>", static_cast<int>(i));
|
||||
bool match = false;
|
||||
for (size_t j = i; j < numWindows; ++j) {
|
||||
if (m_windows[j]->GetId() == id) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (auto win = AddWindow(id, std::make_unique<PlotView>(this))) {
|
||||
win->SetDefaultSize(700, 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,4 @@ void glass::DisplayStringChooser(StringChooserModel* model) {
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ bool DeleteButton(ImGuiID id, const ImVec2& pos) {
|
||||
bool HeaderDeleteButton(const char* label) {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiItemHoveredDataBackup last_item_backup;
|
||||
ImGuiLastItemDataBackup last_item_backup;
|
||||
ImGuiID id = window->GetID(label);
|
||||
float button_size = g.FontSize;
|
||||
float button_x = ImMax(window->DC.LastItemRect.Min.x,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace glass {
|
||||
class Window {
|
||||
public:
|
||||
Window() = default;
|
||||
explicit Window(wpi::StringRef id) : m_id{id} {}
|
||||
explicit Window(wpi::StringRef id) : m_id{id}, m_defaultName{id} {}
|
||||
|
||||
wpi::StringRef GetId() const { return m_id; }
|
||||
|
||||
@@ -44,6 +44,7 @@ class Window {
|
||||
void SetFlags(ImGuiWindowFlags flags) { m_flags = flags; }
|
||||
|
||||
void SetName(const wpi::Twine& name) { m_name = name.str(); }
|
||||
void SetDefaultName(const wpi::Twine& name) { m_defaultName = name.str(); }
|
||||
|
||||
/**
|
||||
* Normally windows provide a right-click popup menu on the title bar to
|
||||
@@ -115,6 +116,7 @@ class Window {
|
||||
private:
|
||||
std::string m_id;
|
||||
std::string m_name;
|
||||
std::string m_defaultName;
|
||||
std::unique_ptr<View> m_view;
|
||||
ImGuiWindowFlags m_flags = 0;
|
||||
bool m_visible = true;
|
||||
|
||||
@@ -4,38 +4,37 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <frc/geometry/Pose2d.h>
|
||||
#include <frc/geometry/Rotation2d.h>
|
||||
#include <frc/geometry/Translation2d.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/ArrayRef.h>
|
||||
#include <wpi/STLExtras.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
class DataSource;
|
||||
|
||||
class FieldObjectModel : public Model {
|
||||
public:
|
||||
virtual DataSource* GetXData() = 0;
|
||||
virtual DataSource* GetYData() = 0;
|
||||
virtual DataSource* GetRotationData() = 0;
|
||||
virtual const char* GetName() const = 0;
|
||||
|
||||
virtual void SetPose(double x, double y, double rot) = 0;
|
||||
virtual void SetPosition(double x, double y) = 0;
|
||||
virtual void SetRotation(double rot) = 0;
|
||||
};
|
||||
|
||||
class FieldObjectGroupModel : public Model {
|
||||
public:
|
||||
virtual void ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model)> func) = 0;
|
||||
virtual wpi::ArrayRef<frc::Pose2d> GetPoses() = 0;
|
||||
virtual void SetPoses(wpi::ArrayRef<frc::Pose2d> poses) = 0;
|
||||
virtual void SetPose(size_t i, frc::Pose2d pose) = 0;
|
||||
virtual void SetPosition(size_t i, frc::Translation2d pos) = 0;
|
||||
virtual void SetRotation(size_t i, frc::Rotation2d rot) = 0;
|
||||
};
|
||||
|
||||
class Field2DModel : public Model {
|
||||
public:
|
||||
virtual void ForEachFieldObjectGroup(
|
||||
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
|
||||
virtual FieldObjectModel* AddFieldObject(const wpi::Twine& name) = 0;
|
||||
virtual void RemoveFieldObject(const wpi::Twine& name) = 0;
|
||||
virtual void ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model, wpi::StringRef name)>
|
||||
func) = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/Twine.h>
|
||||
@@ -20,10 +21,11 @@ class LogData {
|
||||
|
||||
void Clear();
|
||||
void Append(const wpi::Twine& msg);
|
||||
const std::string& GetBuffer();
|
||||
|
||||
private:
|
||||
size_t m_maxLines;
|
||||
std::vector<char> m_buf;
|
||||
std::string m_buf;
|
||||
std::vector<size_t> m_lineOffsets{0};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,20 +5,22 @@
|
||||
#include "glass/networktables/NTField2D.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/Endian.h>
|
||||
#include <wpi/MathExtras.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
class NTField2DModel::GroupModel : public FieldObjectGroupModel {
|
||||
class NTField2DModel::ObjectModel : public FieldObjectModel {
|
||||
public:
|
||||
GroupModel(wpi::StringRef name, NT_Entry entry)
|
||||
ObjectModel(wpi::StringRef name, NT_Entry entry)
|
||||
: m_name{name}, m_entry{entry} {}
|
||||
|
||||
wpi::StringRef GetName() const { return m_name; }
|
||||
const char* GetName() const override { return m_name.c_str(); }
|
||||
NT_Entry GetEntry() const { return m_entry; }
|
||||
|
||||
void NTUpdate(const nt::Value& value);
|
||||
@@ -31,146 +33,118 @@ class NTField2DModel::GroupModel : public FieldObjectGroupModel {
|
||||
bool Exists() override { return nt::GetEntryType(m_entry) != NT_UNASSIGNED; }
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
void ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model)> func) override;
|
||||
wpi::ArrayRef<frc::Pose2d> GetPoses() override { return m_poses; }
|
||||
void SetPoses(wpi::ArrayRef<frc::Pose2d> poses) override;
|
||||
void SetPose(size_t i, frc::Pose2d pose) override;
|
||||
void SetPosition(size_t i, frc::Translation2d pos) override;
|
||||
void SetRotation(size_t i, frc::Rotation2d rot) override;
|
||||
|
||||
private:
|
||||
void UpdateNT();
|
||||
|
||||
std::string m_name;
|
||||
NT_Entry m_entry;
|
||||
|
||||
// keep count of objects rather than resizing vector, as there is a fair
|
||||
// amount of overhead associated with the latter (DataSource record keeping)
|
||||
size_t m_count = 0;
|
||||
class ObjectModel;
|
||||
std::vector<std::unique_ptr<ObjectModel>> m_objects;
|
||||
std::vector<frc::Pose2d> m_poses;
|
||||
};
|
||||
|
||||
class NTField2DModel::GroupModel::ObjectModel : public FieldObjectModel {
|
||||
public:
|
||||
ObjectModel(wpi::StringRef name, NT_Entry entry, int index)
|
||||
: m_entry{entry},
|
||||
m_index{index},
|
||||
m_x{name + "[" + wpi::Twine{index} + "]/x"},
|
||||
m_y{name + "[" + wpi::Twine{index} + "]/y"},
|
||||
m_rot{name + "[" + wpi::Twine{index} + "]/rot"} {}
|
||||
void NTField2DModel::ObjectModel::NTUpdate(const nt::Value& value) {
|
||||
if (value.IsDoubleArray()) {
|
||||
auto arr = value.GetDoubleArray();
|
||||
auto size = arr.size();
|
||||
if ((size % 3) != 0) {
|
||||
return;
|
||||
}
|
||||
m_poses.resize(size / 3);
|
||||
for (size_t i = 0; i < size / 3; ++i) {
|
||||
m_poses[i] = frc::Pose2d{
|
||||
units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
|
||||
frc::Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
|
||||
}
|
||||
} else if (value.IsRaw()) {
|
||||
// treat it simply as an array of doubles
|
||||
wpi::StringRef data = value.GetRaw();
|
||||
|
||||
void SetExists(bool exists) { m_exists = exists; }
|
||||
|
||||
void Update() override {}
|
||||
bool Exists() override { return m_exists; }
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
DataSource* GetXData() override { return &m_x; }
|
||||
DataSource* GetYData() override { return &m_y; }
|
||||
DataSource* GetRotationData() override { return &m_rot; }
|
||||
|
||||
void SetPose(double x, double y, double rot) override;
|
||||
void SetPosition(double x, double y) override;
|
||||
void SetRotation(double rot) override;
|
||||
|
||||
private:
|
||||
void SetPoseImpl(double x, double y, double rot, bool setX, bool setY,
|
||||
bool setRot);
|
||||
|
||||
NT_Entry m_entry;
|
||||
int m_index;
|
||||
bool m_exists = true;
|
||||
|
||||
public:
|
||||
DataSource m_x;
|
||||
DataSource m_y;
|
||||
DataSource m_rot;
|
||||
};
|
||||
|
||||
void NTField2DModel::GroupModel::NTUpdate(const nt::Value& value) {
|
||||
if (!value.IsDoubleArray()) {
|
||||
m_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto arr = value.GetDoubleArray();
|
||||
// must be triples
|
||||
if ((arr.size() % 3) != 0) {
|
||||
m_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
m_count = arr.size() / 3;
|
||||
if (m_count > m_objects.size()) {
|
||||
m_objects.reserve(m_count);
|
||||
for (size_t i = m_objects.size(); i < m_count; ++i) {
|
||||
m_objects.emplace_back(std::make_unique<ObjectModel>(m_name, m_entry, i));
|
||||
// must be triples of doubles
|
||||
auto size = data.size();
|
||||
if ((size % (3 * 8)) != 0) {
|
||||
return;
|
||||
}
|
||||
m_poses.resize(size / (3 * 8));
|
||||
const char* p = data.begin();
|
||||
for (size_t i = 0; i < size / (3 * 8); ++i) {
|
||||
double x = wpi::BitsToDouble(
|
||||
wpi::support::endian::readNext<uint64_t, wpi::support::big,
|
||||
wpi::support::unaligned>(p));
|
||||
double y = wpi::BitsToDouble(
|
||||
wpi::support::endian::readNext<uint64_t, wpi::support::big,
|
||||
wpi::support::unaligned>(p));
|
||||
double rot = wpi::BitsToDouble(
|
||||
wpi::support::endian::readNext<uint64_t, wpi::support::big,
|
||||
wpi::support::unaligned>(p));
|
||||
m_poses[i] = frc::Pose2d{units::meter_t{x}, units::meter_t{y},
|
||||
frc::Rotation2d{units::degree_t{rot}}};
|
||||
}
|
||||
}
|
||||
if (m_count < m_objects.size()) {
|
||||
for (size_t i = m_count; i < m_objects.size(); ++i) {
|
||||
m_objects[i]->SetExists(false);
|
||||
}
|
||||
|
||||
void NTField2DModel::ObjectModel::UpdateNT() {
|
||||
if (m_poses.size() < (255 / 3)) {
|
||||
wpi::SmallVector<double, 9> arr;
|
||||
for (auto&& pose : m_poses) {
|
||||
auto& translation = pose.Translation();
|
||||
arr.push_back(translation.X().to<double>());
|
||||
arr.push_back(translation.Y().to<double>());
|
||||
arr.push_back(pose.Rotation().Degrees().to<double>());
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_count; ++i) {
|
||||
auto& obj = m_objects[i];
|
||||
obj->SetExists(true);
|
||||
obj->m_x.SetValue(arr[i * 3], value.last_change());
|
||||
obj->m_y.SetValue(arr[i * 3 + 1], value.last_change());
|
||||
obj->m_rot.SetValue(arr[i * 3 + 2], value.last_change());
|
||||
nt::SetEntryTypeValue(m_entry, nt::Value::MakeDoubleArray(arr));
|
||||
} else {
|
||||
// send as raw array of doubles if too big for NT array
|
||||
std::vector<char> arr;
|
||||
arr.resize(m_poses.size() * 3 * 8);
|
||||
char* p = arr.data();
|
||||
for (auto&& pose : m_poses) {
|
||||
auto& translation = pose.Translation();
|
||||
wpi::support::endian::write64be(
|
||||
p, wpi::DoubleToBits(translation.X().to<double>()));
|
||||
p += 8;
|
||||
wpi::support::endian::write64be(
|
||||
p, wpi::DoubleToBits(translation.Y().to<double>()));
|
||||
p += 8;
|
||||
wpi::support::endian::write64be(
|
||||
p, wpi::DoubleToBits(pose.Rotation().Degrees().to<double>()));
|
||||
p += 8;
|
||||
}
|
||||
nt::SetEntryTypeValue(
|
||||
m_entry, nt::Value::MakeRaw(wpi::StringRef{arr.data(), arr.size()}));
|
||||
}
|
||||
}
|
||||
|
||||
void NTField2DModel::GroupModel::ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model)> func) {
|
||||
for (size_t i = 0; i < m_count; ++i) {
|
||||
func(*m_objects[i]);
|
||||
void NTField2DModel::ObjectModel::SetPoses(wpi::ArrayRef<frc::Pose2d> poses) {
|
||||
m_poses = poses;
|
||||
UpdateNT();
|
||||
}
|
||||
|
||||
void NTField2DModel::ObjectModel::SetPose(size_t i, frc::Pose2d pose) {
|
||||
if (i < m_poses.size()) {
|
||||
m_poses[i] = pose;
|
||||
UpdateNT();
|
||||
}
|
||||
}
|
||||
|
||||
void NTField2DModel::GroupModel::ObjectModel::SetPose(double x, double y,
|
||||
double rot) {
|
||||
SetPoseImpl(x, y, rot, true, true, true);
|
||||
void NTField2DModel::ObjectModel::SetPosition(size_t i,
|
||||
frc::Translation2d pos) {
|
||||
if (i < m_poses.size()) {
|
||||
m_poses[i] = frc::Pose2d{pos, m_poses[i].Rotation()};
|
||||
UpdateNT();
|
||||
}
|
||||
}
|
||||
|
||||
void NTField2DModel::GroupModel::ObjectModel::SetPosition(double x, double y) {
|
||||
SetPoseImpl(x, y, 0, true, true, false);
|
||||
}
|
||||
|
||||
void NTField2DModel::GroupModel::ObjectModel::SetRotation(double rot) {
|
||||
SetPoseImpl(0, 0, rot, false, false, true);
|
||||
}
|
||||
|
||||
void NTField2DModel::GroupModel::ObjectModel::SetPoseImpl(double x, double y,
|
||||
double rot, bool setX,
|
||||
bool setY,
|
||||
bool setRot) {
|
||||
// get from NT, validate type and size
|
||||
auto value = nt::GetEntryValue(m_entry);
|
||||
if (!value || !value->IsDoubleArray()) {
|
||||
return;
|
||||
void NTField2DModel::ObjectModel::SetRotation(size_t i, frc::Rotation2d rot) {
|
||||
if (i < m_poses.size()) {
|
||||
m_poses[i] = frc::Pose2d{m_poses[i].Translation(), rot};
|
||||
UpdateNT();
|
||||
}
|
||||
auto origArr = value->GetDoubleArray();
|
||||
if (static_cast<int>(origArr.size()) < ((m_index + 1) * 3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// copy existing array
|
||||
wpi::SmallVector<double, 8> arr;
|
||||
arr.reserve(origArr.size());
|
||||
for (auto&& elem : origArr) {
|
||||
arr.emplace_back(elem);
|
||||
}
|
||||
|
||||
// update value
|
||||
if (setX) {
|
||||
arr[m_index * 3 + 0] = x;
|
||||
}
|
||||
if (setY) {
|
||||
arr[m_index * 3 + 1] = y;
|
||||
}
|
||||
if (setRot) {
|
||||
arr[m_index * 3 + 2] = rot;
|
||||
}
|
||||
|
||||
// set back to NT
|
||||
nt::SetEntryValue(m_entry, nt::Value::MakeDoubleArray(arr));
|
||||
}
|
||||
|
||||
NTField2DModel::NTField2DModel(wpi::StringRef path)
|
||||
@@ -199,9 +173,9 @@ void NTField2DModel::Update() {
|
||||
// common case: update of existing entry; search by entry
|
||||
if (event.flags & NT_NOTIFY_UPDATE) {
|
||||
auto it = std::find_if(
|
||||
m_groups.begin(), m_groups.end(),
|
||||
m_objects.begin(), m_objects.end(),
|
||||
[&](const auto& e) { return e->GetEntry() == event.entry; });
|
||||
if (it != m_groups.end()) {
|
||||
if (it != m_objects.end()) {
|
||||
(*it)->NTUpdate(*event.value);
|
||||
continue;
|
||||
}
|
||||
@@ -213,20 +187,16 @@ void NTField2DModel::Update() {
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
auto it = std::lower_bound(m_groups.begin(), m_groups.end(), name,
|
||||
[](const auto& e, wpi::StringRef name) {
|
||||
return e->GetName() < name;
|
||||
});
|
||||
bool match = (it != m_groups.end() && (*it)->GetName() == name);
|
||||
auto [it, match] = Find(event.name);
|
||||
if (event.flags & NT_NOTIFY_DELETE) {
|
||||
if (match) {
|
||||
m_groups.erase(it);
|
||||
m_objects.erase(it);
|
||||
}
|
||||
continue;
|
||||
} else if (event.flags & NT_NOTIFY_NEW) {
|
||||
if (!match) {
|
||||
it = m_groups.emplace(
|
||||
it, std::make_unique<GroupModel>(event.name, event.entry));
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(event.name, event.entry));
|
||||
}
|
||||
} else if (!match) {
|
||||
continue;
|
||||
@@ -246,12 +216,40 @@ bool NTField2DModel::IsReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NTField2DModel::ForEachFieldObjectGroup(
|
||||
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
|
||||
FieldObjectModel* NTField2DModel::AddFieldObject(const wpi::Twine& name) {
|
||||
wpi::SmallString<128> fullNameBuf;
|
||||
wpi::StringRef fullName = (m_path + name).toStringRef(fullNameBuf);
|
||||
auto [it, match] = Find(fullName);
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(fullName, m_nt.GetEntry(fullName)));
|
||||
}
|
||||
return it->get();
|
||||
}
|
||||
|
||||
void NTField2DModel::RemoveFieldObject(const wpi::Twine& name) {
|
||||
wpi::SmallString<128> fullNameBuf;
|
||||
auto [it, match] = Find((m_path + name).toStringRef(fullNameBuf));
|
||||
if (match) {
|
||||
nt::DeleteEntry((*it)->GetEntry());
|
||||
m_objects.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void NTField2DModel::ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model, wpi::StringRef name)>
|
||||
func) {
|
||||
for (auto&& group : m_groups) {
|
||||
if (group->Exists()) {
|
||||
func(*group, wpi::StringRef{group->GetName()}.drop_front(m_path.size()));
|
||||
for (auto&& obj : m_objects) {
|
||||
if (obj->Exists()) {
|
||||
func(*obj, wpi::StringRef{obj->GetName()}.drop_front(m_path.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<NTField2DModel::Objects::iterator, bool> NTField2DModel::Find(
|
||||
wpi::StringRef fullName) {
|
||||
auto it = std::lower_bound(
|
||||
m_objects.begin(), m_objects.end(), fullName,
|
||||
[](const auto& e, wpi::StringRef name) { return e->GetName() < name; });
|
||||
return {it, it != m_objects.end() && (*it)->GetName() == fullName};
|
||||
}
|
||||
|
||||
@@ -39,17 +39,30 @@ void NTStringChooserModel::SetOptions(wpi::ArrayRef<std::string> val) {
|
||||
|
||||
void NTStringChooserModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_default && event.value && event.value->IsString()) {
|
||||
m_defaultValue = event.value->GetString();
|
||||
} else if (event.entry == m_selected && event.value &&
|
||||
event.value->IsString()) {
|
||||
m_selectedValue = event.value->GetString();
|
||||
} else if (event.entry == m_active && event.value &&
|
||||
event.value->IsString()) {
|
||||
m_activeValue = event.value->GetString();
|
||||
} else if (event.entry == m_options && event.value &&
|
||||
event.value->IsStringArray()) {
|
||||
m_optionsValue = event.value->GetStringArray();
|
||||
if (event.entry == m_default) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
|
||||
m_defaultValue.clear();
|
||||
} else if (event.value && event.value->IsString()) {
|
||||
m_defaultValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_selected) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
|
||||
m_selectedValue.clear();
|
||||
} else if (event.value && event.value->IsString()) {
|
||||
m_selectedValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_active) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
|
||||
m_activeValue.clear();
|
||||
} else if (event.value && event.value->IsString()) {
|
||||
m_activeValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_options) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
|
||||
m_optionsValue.clear();
|
||||
} else if (event.value && event.value->IsStringArray()) {
|
||||
m_optionsValue = event.value->GetStringArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,8 +266,8 @@ static std::shared_ptr<nt::Value> StringToBooleanArray(wpi::StringRef in) {
|
||||
static std::shared_ptr<nt::Value> StringToDoubleArray(wpi::StringRef in) {
|
||||
in = in.trim();
|
||||
if (in.empty()) {
|
||||
return nt::NetworkTableValue::MakeBooleanArray(
|
||||
std::initializer_list<bool>{});
|
||||
return nt::NetworkTableValue::MakeDoubleArray(
|
||||
std::initializer_list<double>{});
|
||||
}
|
||||
if (in.front() == '[') {
|
||||
in = in.drop_front();
|
||||
|
||||
@@ -153,6 +153,10 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
if (wpi::StringRef{entry->name}.startswith("/SmartDashboard/")) {
|
||||
window->SetDefaultName(wpi::StringRef{entry->name}.drop_front(16) +
|
||||
" (SmartDashboard)");
|
||||
}
|
||||
entry->window = window;
|
||||
|
||||
// create view
|
||||
|
||||
131
glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
Normal file
131
glass/src/libnt/native/cpp/NetworkTablesSettings.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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 "glass/networktables/NetworkTablesSettings.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/StringRef.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NetworkTablesSettings::Thread::Main() {
|
||||
while (m_active) {
|
||||
// wait to be woken up
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cond.wait(lock, [&] { return !m_active || m_restart; });
|
||||
if (!m_active) {
|
||||
break;
|
||||
}
|
||||
|
||||
// clear restart flag
|
||||
m_restart = false;
|
||||
|
||||
int mode;
|
||||
bool dsClient;
|
||||
|
||||
do {
|
||||
mode = m_mode;
|
||||
dsClient = m_dsClient;
|
||||
|
||||
// release lock while stopping to avoid blocking GUI
|
||||
lock.unlock();
|
||||
|
||||
// if just changing servers in client mode, no need to stop and restart
|
||||
unsigned int curMode = nt::GetNetworkMode(m_inst);
|
||||
if (mode != 1 || (curMode & NT_NET_MODE_SERVER) != 0) {
|
||||
nt::StopClient(m_inst);
|
||||
nt::StopServer(m_inst);
|
||||
nt::StopLocal(m_inst);
|
||||
}
|
||||
|
||||
if (m_mode != 1 || !dsClient) {
|
||||
nt::StopDSClient(m_inst);
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
} while (mode != m_mode || dsClient != m_dsClient);
|
||||
|
||||
if (m_mode == 1) {
|
||||
wpi::StringRef serverTeam{m_serverTeam};
|
||||
unsigned int team;
|
||||
if (!serverTeam.contains('.') && !serverTeam.getAsInteger(10, team)) {
|
||||
nt::StartClientTeam(m_inst, team, NT_DEFAULT_PORT);
|
||||
} else {
|
||||
wpi::SmallVector<wpi::StringRef, 4> serverNames;
|
||||
wpi::SmallVector<std::pair<wpi::StringRef, unsigned int>, 4> servers;
|
||||
serverTeam.split(serverNames, ',', -1, false);
|
||||
for (auto&& serverName : serverNames) {
|
||||
servers.emplace_back(serverName, NT_DEFAULT_PORT);
|
||||
}
|
||||
nt::StartClient(m_inst, servers);
|
||||
}
|
||||
|
||||
if (m_dsClient) {
|
||||
nt::StartDSClient(m_inst, NT_DEFAULT_PORT);
|
||||
}
|
||||
} else if (m_mode == 2) {
|
||||
nt::StartServer(m_inst, m_iniName.c_str(), m_listenAddress.c_str(),
|
||||
NT_DEFAULT_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst,
|
||||
const char* storageName) {
|
||||
auto& storage = glass::GetStorage(storageName);
|
||||
m_pMode = storage.GetIntRef("mode");
|
||||
m_pIniName = storage.GetStringRef("iniName", "networktables.ini");
|
||||
m_pServerTeam = storage.GetStringRef("serverTeam");
|
||||
m_pListenAddress = storage.GetStringRef("listenAddress");
|
||||
m_pDsClient = storage.GetBoolRef("dsClient", true);
|
||||
|
||||
m_thread.Start(inst);
|
||||
}
|
||||
|
||||
void NetworkTablesSettings::Update() {
|
||||
if (!m_restart) {
|
||||
return;
|
||||
}
|
||||
m_restart = false;
|
||||
|
||||
// do actual operation on thread
|
||||
auto thr = m_thread.GetThread();
|
||||
thr->m_restart = true;
|
||||
thr->m_mode = *m_pMode;
|
||||
thr->m_iniName = *m_pIniName;
|
||||
thr->m_serverTeam = *m_pServerTeam;
|
||||
thr->m_listenAddress = *m_pListenAddress;
|
||||
thr->m_dsClient = *m_pDsClient;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
bool NetworkTablesSettings::Display() {
|
||||
static const char* modeOptions[] = {"Disabled", "Client", "Server"};
|
||||
ImGui::Combo("Mode", m_pMode, modeOptions, m_serverOption ? 3 : 2);
|
||||
switch (*m_pMode) {
|
||||
case 1:
|
||||
ImGui::InputText("Team/IP", m_pServerTeam);
|
||||
ImGui::Checkbox("Get Address from DS", m_pDsClient);
|
||||
break;
|
||||
case 2:
|
||||
ImGui::InputText("Listen Address", m_pListenAddress);
|
||||
ImGui::InputText("ini Filename", m_pIniName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ImGui::Button("Apply")) {
|
||||
m_restart = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
@@ -32,8 +33,10 @@ class NTField2DModel : public Field2DModel {
|
||||
bool Exists() override;
|
||||
bool IsReadOnly() override;
|
||||
|
||||
void ForEachFieldObjectGroup(
|
||||
wpi::function_ref<void(FieldObjectGroupModel& model, wpi::StringRef name)>
|
||||
FieldObjectModel* AddFieldObject(const wpi::Twine& name) override;
|
||||
void RemoveFieldObject(const wpi::Twine& name) override;
|
||||
void ForEachFieldObject(
|
||||
wpi::function_ref<void(FieldObjectModel& model, wpi::StringRef name)>
|
||||
func) override;
|
||||
|
||||
private:
|
||||
@@ -42,8 +45,11 @@ class NTField2DModel : public Field2DModel {
|
||||
NT_Entry m_name;
|
||||
std::string m_nameValue;
|
||||
|
||||
class GroupModel;
|
||||
std::vector<std::unique_ptr<GroupModel>> m_groups;
|
||||
class ObjectModel;
|
||||
using Objects = std::vector<std::unique_ptr<ObjectModel>>;
|
||||
Objects m_objects;
|
||||
|
||||
std::pair<Objects::iterator, bool> Find(wpi::StringRef fullName);
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -28,7 +28,8 @@ class NetworkTablesHelper {
|
||||
}
|
||||
|
||||
static constexpr int kDefaultListenerFlags =
|
||||
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE;
|
||||
NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_UPDATE | NT_NOTIFY_DELETE |
|
||||
NT_NOTIFY_IMMEDIATE;
|
||||
|
||||
NT_EntryListener AddListener(NT_Entry entry,
|
||||
unsigned int flags = kDefaultListenerFlags) {
|
||||
|
||||
@@ -7,26 +7,53 @@
|
||||
#include <string>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/SafeThread.h>
|
||||
|
||||
namespace wpi {
|
||||
template <typename T>
|
||||
class SmallVectorImpl;
|
||||
} // namespace wpi
|
||||
|
||||
namespace glass {
|
||||
|
||||
class NetworkTablesSettings {
|
||||
public:
|
||||
explicit NetworkTablesSettings(
|
||||
NT_Inst inst = nt::GetDefaultInstance(),
|
||||
const char* storageName = "NetworkTables Settings");
|
||||
|
||||
/**
|
||||
* Enables or disables the server option. Default is enabled.
|
||||
*/
|
||||
void EnableServerOption(bool enable) { m_serverOption = enable; }
|
||||
|
||||
void Update();
|
||||
void Display();
|
||||
bool Display();
|
||||
|
||||
private:
|
||||
NT_Inst m_inst;
|
||||
bool m_restart = true;
|
||||
bool m_serverOption = true;
|
||||
int* m_pMode;
|
||||
std::string* m_pIniName;
|
||||
std::string* m_pServerTeam;
|
||||
std::string* m_pListenAddress;
|
||||
bool* m_pDsClient;
|
||||
|
||||
class Thread : public wpi::SafeThread {
|
||||
public:
|
||||
explicit Thread(NT_Inst inst) : m_inst{inst} {}
|
||||
|
||||
void Main() override;
|
||||
|
||||
NT_Inst m_inst;
|
||||
bool m_restart = false;
|
||||
int m_mode;
|
||||
std::string m_iniName;
|
||||
std::string m_serverTeam;
|
||||
std::string m_listenAddress;
|
||||
bool m_dsClient;
|
||||
};
|
||||
wpi::SafeThreadOwner<Thread> m_thread;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
@@ -14,6 +14,9 @@ public class NotifierJNI extends JNIWrapper {
|
||||
/** Initializes the notifier. */
|
||||
public static native int initializeNotifier();
|
||||
|
||||
/** Sets the HAL notifier thread priority. */
|
||||
public static native boolean setHALThreadPriority(boolean realTime, int priority);
|
||||
|
||||
/** Sets the name of the notifier. */
|
||||
public static native void setNotifierName(int notifierHandle, String name);
|
||||
|
||||
|
||||
@@ -4,7 +4,17 @@
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
/** A wrapper around a simulator device handle. */
|
||||
/**
|
||||
* A wrapper around a simulator device handle.
|
||||
*
|
||||
* <p>Teams: if you are using this class, you are likely confusing it for {@link
|
||||
* edu.wpi.first.wpilibj.simulation.SimDeviceSim}.
|
||||
*
|
||||
* <p>Vendors: This class should be used from inside the device class to define the
|
||||
* properties/fields of the device. Use {@link #create} to get a SimDevice object, then use {@link
|
||||
* #createDouble(String, Direction, double)} or similar to define the device's fields. See {@link
|
||||
* edu.wpi.first.wpilibj.ADXRS450_Gyro} for an example implementation.
|
||||
*/
|
||||
public class SimDevice implements AutoCloseable {
|
||||
public enum Direction {
|
||||
kInput(SimDeviceJNI.kInput),
|
||||
@@ -130,6 +140,42 @@ public class SimDevice implements AutoCloseable {
|
||||
return new SimValue(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an int value on the simulated device.
|
||||
*
|
||||
* <p>Returns null if not in simulation.
|
||||
*
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated double value object
|
||||
*/
|
||||
public SimInt createInt(String name, Direction direction, int initialValue) {
|
||||
int handle = SimDeviceJNI.createSimValueInt(m_handle, name, direction.m_value, initialValue);
|
||||
if (handle <= 0) {
|
||||
return null;
|
||||
}
|
||||
return new SimInt(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a long value on the simulated device.
|
||||
*
|
||||
* <p>Returns null if not in simulation.
|
||||
*
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated double value object
|
||||
*/
|
||||
public SimLong createLong(String name, Direction direction, long initialValue) {
|
||||
int handle = SimDeviceJNI.createSimValueLong(m_handle, name, direction.m_value, initialValue);
|
||||
if (handle <= 0) {
|
||||
return null;
|
||||
}
|
||||
return new SimLong(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double value on the simulated device.
|
||||
*
|
||||
|
||||
@@ -81,6 +81,36 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
initialValue.getNativeDouble());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an int value on a simulated device.
|
||||
*
|
||||
* <p>Returns 0 if not in simulation; this can be used to avoid calls to Set/Get functions.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated value handle
|
||||
*/
|
||||
public static int createSimValueInt(int device, String name, int direction, int initialValue) {
|
||||
return createSimValueNative(device, name, direction, HALValue.kInt, initialValue, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a long value on a simulated device.
|
||||
*
|
||||
* <p>Returns 0 if not in simulation; this can be used to avoid calls to Set/Get functions.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated value handle
|
||||
*/
|
||||
public static int createSimValueLong(int device, String name, int direction, long initialValue) {
|
||||
return createSimValueNative(device, name, direction, HALValue.kLong, initialValue, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double value on a simulated device.
|
||||
*
|
||||
@@ -221,6 +251,22 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
*/
|
||||
public static native HALValue getSimValue(int handle);
|
||||
|
||||
/**
|
||||
* Gets a simulated value (int).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @return The current value
|
||||
*/
|
||||
public static native int getSimValueInt(int handle);
|
||||
|
||||
/**
|
||||
* Gets a simulated value (long).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @return The current value
|
||||
*/
|
||||
public static native long getSimValueLong(int handle);
|
||||
|
||||
/**
|
||||
* Gets a simulated value (double).
|
||||
*
|
||||
@@ -257,6 +303,26 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
setSimValueNative(handle, value.getType(), value.getNativeLong(), value.getNativeDouble());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a simulated value (int).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param value the value to set
|
||||
*/
|
||||
public static void setSimValueInt(int handle, int value) {
|
||||
setSimValueNative(handle, HALValue.kInt, value, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a simulated value (long).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param value the value to set
|
||||
*/
|
||||
public static void setSimValueLong(int handle, long value) {
|
||||
setSimValueNative(handle, HALValue.kLong, value, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a simulated value (double).
|
||||
*
|
||||
@@ -286,4 +352,13 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
public static void setSimValueBoolean(int handle, boolean value) {
|
||||
setSimValueNative(handle, HALValue.kBoolean, value ? 1 : 0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a simulated double or integral value to 0. Has no effect on other value types. Use this
|
||||
* instead of Set(0) for resetting incremental sensor values like encoder counts or gyro
|
||||
* accumulated angle to ensure correct behavior in a distributed system (e.g. WebSockets).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
public static native void resetSimValue(int handle);
|
||||
}
|
||||
|
||||
@@ -32,4 +32,13 @@ public class SimDouble extends SimValue {
|
||||
public void set(double value) {
|
||||
SimDeviceJNI.setSimValueDouble(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting incremental sensor
|
||||
* values like encoder counts or gyro accumulated angle to ensure correct behavior in a
|
||||
* distributed system (e.g. WebSockets).
|
||||
*/
|
||||
public void reset() {
|
||||
SimDeviceJNI.resetSimValue(m_handle);
|
||||
}
|
||||
}
|
||||
|
||||
44
hal/src/main/java/edu/wpi/first/hal/SimInt.java
Normal file
44
hal/src/main/java/edu/wpi/first/hal/SimInt.java
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
/** A wrapper around a simulator int value handle. */
|
||||
public class SimInt extends SimValue {
|
||||
/**
|
||||
* Wraps a simulated value handle as returned by SimDeviceJNI.createSimValueInt().
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
public SimInt(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the simulated value.
|
||||
*
|
||||
* @return The current value
|
||||
*/
|
||||
public int get() {
|
||||
return SimDeviceJNI.getSimValueInt(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the simulated value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void set(int value) {
|
||||
SimDeviceJNI.setSimValueInt(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting incremental sensor
|
||||
* values like encoder counts or gyro accumulated angle to ensure correct behavior in a
|
||||
* distributed system (e.g. WebSockets).
|
||||
*/
|
||||
public void reset() {
|
||||
SimDeviceJNI.resetSimValue(m_handle);
|
||||
}
|
||||
}
|
||||
44
hal/src/main/java/edu/wpi/first/hal/SimLong.java
Normal file
44
hal/src/main/java/edu/wpi/first/hal/SimLong.java
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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.
|
||||
|
||||
package edu.wpi.first.hal;
|
||||
|
||||
/** A wrapper around a simulator long value handle. */
|
||||
public class SimLong extends SimValue {
|
||||
/**
|
||||
* Wraps a simulated value handle as returned by SimDeviceJNI.createSimValueLong().
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
public SimLong(int handle) {
|
||||
super(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the simulated value.
|
||||
*
|
||||
* @return The current value
|
||||
*/
|
||||
public long get() {
|
||||
return SimDeviceJNI.getSimValueLong(m_handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the simulated value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
public void set(long value) {
|
||||
SimDeviceJNI.setSimValueLong(m_handle, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting incremental sensor
|
||||
* values like encoder counts or gyro accumulated angle to ensure correct behavior in a
|
||||
* distributed system (e.g. WebSockets).
|
||||
*/
|
||||
public void reset() {
|
||||
SimDeviceJNI.resetSimValue(m_handle);
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,17 @@ package edu.wpi.first.hal.simulation;
|
||||
import edu.wpi.first.hal.JNIWrapper;
|
||||
|
||||
public class RoboRioDataJNI extends JNIWrapper {
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public static native int registerFPGAButtonCallback(
|
||||
NotifyCallback callback, boolean initialNotify);
|
||||
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public static native void cancelFPGAButtonCallback(int uid);
|
||||
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public static native boolean getFPGAButton();
|
||||
|
||||
@SuppressWarnings("AbbreviationAsWordInName")
|
||||
public static native void setFPGAButton(boolean fPGAButton);
|
||||
|
||||
public static native int registerVInVoltageCallback(
|
||||
|
||||
@@ -29,6 +29,7 @@ public class SimDeviceDataJNI extends JNIWrapper {
|
||||
public static native int getSimValueDeviceHandle(int handle);
|
||||
|
||||
public static class SimDeviceInfo {
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
public SimDeviceInfo(String name, int handle) {
|
||||
this.name = name;
|
||||
this.handle = handle;
|
||||
@@ -59,9 +60,23 @@ public class SimDeviceDataJNI extends JNIWrapper {
|
||||
|
||||
public static native void cancelSimValueChangedCallback(int uid);
|
||||
|
||||
/**
|
||||
* Register a callback for SimDeviceJNI.resetSimValue(). The callback is called with the old
|
||||
* value.
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param callback callback
|
||||
* @param initialNotify ignored (present for consistency)
|
||||
*/
|
||||
public static native int registerSimValueResetCallback(
|
||||
int handle, SimValueCallback2 callback, boolean initialNotify);
|
||||
|
||||
public static native void cancelSimValueResetCallback(int uid);
|
||||
|
||||
public static native int getSimValueHandle(int device, String name);
|
||||
|
||||
public static class SimValueInfo {
|
||||
@SuppressWarnings("JavadocMethod")
|
||||
public SimValueInfo(
|
||||
String name, int handle, int direction, int type, long value1, double value2) {
|
||||
this.name = name;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "hal/ChipObject.h"
|
||||
#include "hal/Errors.h"
|
||||
#include "hal/HAL.h"
|
||||
#include "hal/Threads.h"
|
||||
#include "hal/handles/UnlimitedHandleResource.h"
|
||||
|
||||
using namespace hal;
|
||||
@@ -156,6 +157,12 @@ HAL_NotifierHandle HAL_InitializeNotifier(int32_t* status) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
HAL_Bool HAL_SetNotifierThreadPriority(HAL_Bool realTime, int32_t priority,
|
||||
int32_t* status) {
|
||||
auto native = notifierThread.native_handle();
|
||||
return HAL_SetThreadPriority(&native, realTime, priority, status);
|
||||
}
|
||||
|
||||
void HAL_SetNotifierName(HAL_NotifierHandle notifierHandle, const char* name,
|
||||
int32_t* status) {}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ void HAL_GetSimValue(HAL_SimValueHandle handle, struct HAL_Value* value) {
|
||||
void HAL_SetSimValue(HAL_SimValueHandle handle, const struct HAL_Value* value) {
|
||||
}
|
||||
|
||||
void HAL_ResetSimValue(HAL_SimValueHandle handle) {}
|
||||
|
||||
hal::SimDevice::SimDevice(const char* name, int index) {}
|
||||
|
||||
hal::SimDevice::SimDevice(const char* name, int index, int channel) {}
|
||||
|
||||
@@ -32,8 +32,8 @@ int32_t HAL_GetThreadPriority(NativeThreadHandle handle, HAL_Bool* isRealTime,
|
||||
return sch.sched_priority;
|
||||
} else {
|
||||
*isRealTime = false;
|
||||
// 0 is the only suppored priority for non-realtime, so scale to 1
|
||||
return 1;
|
||||
// 0 is the only supported priority for non-real-time
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,15 @@ int32_t HALSIM_RegisterSimValueChangedCallback(HAL_SimValueHandle handle,
|
||||
|
||||
void HALSIM_CancelSimValueChangedCallback(int32_t uid) {}
|
||||
|
||||
int32_t HALSIM_RegisterSimValueResetCallback(HAL_SimValueHandle handle,
|
||||
void* param,
|
||||
HALSIM_SimValueCallback callback,
|
||||
HAL_Bool initialNotify) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HALSIM_CancelSimValueResetCallback(int32_t uid) {}
|
||||
|
||||
HAL_SimValueHandle HALSIM_GetSimValueHandle(HAL_SimDeviceHandle device,
|
||||
const char* name) {
|
||||
return 0;
|
||||
|
||||
@@ -36,6 +36,19 @@ Java_edu_wpi_first_hal_NotifierJNI_initializeNotifier
|
||||
return (jint)notifierHandle;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_NotifierJNI
|
||||
* Method: setHALThreadPriority
|
||||
* Signature: (ZI)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_edu_wpi_first_hal_NotifierJNI_setHALThreadPriority
|
||||
(JNIEnv* env, jclass, jboolean realTime, jint priority)
|
||||
{
|
||||
int32_t status = 0;
|
||||
return HAL_SetNotifierThreadPriority(realTime, priority, &status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_NotifierJNI
|
||||
* Method: setNotifierName
|
||||
|
||||
@@ -153,6 +153,30 @@ Java_edu_wpi_first_hal_SimDeviceJNI_getSimValue
|
||||
return hal::CreateHALValue(env, HAL_GetSimValue(handle));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: getSimValueInt
|
||||
* Signature: (I)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_SimDeviceJNI_getSimValueInt
|
||||
(JNIEnv*, jclass, jint handle)
|
||||
{
|
||||
return HAL_GetSimValueInt(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: getSimValueLong
|
||||
* Signature: (I)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_hal_SimDeviceJNI_getSimValueLong
|
||||
(JNIEnv*, jclass, jint handle)
|
||||
{
|
||||
return HAL_GetSimValueLong(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: getSimValueDouble
|
||||
@@ -201,4 +225,16 @@ Java_edu_wpi_first_hal_SimDeviceJNI_setSimValueNative
|
||||
HAL_SetSimValue(handle, ValueFromJava(type, value1, value2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_SimDeviceJNI
|
||||
* Method: resetSimValue
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_SimDeviceJNI_resetSimValue
|
||||
(JNIEnv*, jclass, jint handle)
|
||||
{
|
||||
HAL_ResetSimValue(handle);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -600,6 +600,32 @@ Java_edu_wpi_first_hal_simulation_SimDeviceDataJNI_cancelSimValueChangedCallback
|
||||
FreeValueCallback(env, uid, &HALSIM_CancelSimValueChangedCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_SimDeviceDataJNI
|
||||
* Method: registerSimValueResetCallback
|
||||
* Signature: (ILjava/lang/Object;Z)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_SimDeviceDataJNI_registerSimValueResetCallback
|
||||
(JNIEnv* env, jclass, jint handle, jobject callback, jboolean initialNotify)
|
||||
{
|
||||
return AllocateValueCallback(env, static_cast<HAL_SimValueHandle>(handle),
|
||||
callback, true, initialNotify,
|
||||
&HALSIM_RegisterSimValueResetCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_SimDeviceDataJNI
|
||||
* Method: cancelSimValueResetCallback
|
||||
* Signature: (I)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_hal_simulation_SimDeviceDataJNI_cancelSimValueResetCallback
|
||||
(JNIEnv* env, jclass, jint uid)
|
||||
{
|
||||
FreeValueCallback(env, uid, &HALSIM_CancelSimValueResetCallback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_hal_simulation_SimDeviceDataJNI
|
||||
* Method: getSimValueHandle
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <wpi/nodiscard.h>
|
||||
|
||||
#include "hal/Types.h"
|
||||
|
||||
/**
|
||||
@@ -28,6 +30,25 @@ extern "C" {
|
||||
*/
|
||||
HAL_NotifierHandle HAL_InitializeNotifier(int32_t* status);
|
||||
|
||||
/**
|
||||
* Sets the HAL notifier thread priority.
|
||||
*
|
||||
* The HAL notifier thread is responsible for managing the FPGA's notifier
|
||||
* interrupt and waking up user's Notifiers when it's their time to run.
|
||||
* Giving the HAL notifier thread real-time priority helps ensure the user's
|
||||
* real-time Notifiers, if any, are notified to run in a timely manner.
|
||||
*
|
||||
* @param realTime Set to true to set a real-time priority, false for standard
|
||||
* priority.
|
||||
* @param priority Priority to set the thread to. For real-time, this is 1-99
|
||||
* with 99 being highest. For non-real-time, this is forced to
|
||||
* 0. See "man 7 sched" for more details.
|
||||
* @param status Error status variable. 0 on success.
|
||||
* @return True on success.
|
||||
*/
|
||||
HAL_Bool HAL_SetNotifierThreadPriority(HAL_Bool realTime, int32_t priority,
|
||||
int32_t* status);
|
||||
|
||||
/**
|
||||
* Sets the name of a notifier.
|
||||
*
|
||||
@@ -80,11 +101,14 @@ void HAL_CancelNotifierAlarm(HAL_NotifierHandle notifierHandle,
|
||||
* Waits for the next alarm for the specific notifier.
|
||||
*
|
||||
* This is a blocking call until either the time elapses or HAL_StopNotifier
|
||||
* gets called.
|
||||
* gets called. If the latter occurs, this function will return zero and any
|
||||
* loops using this function should exit. Failing to do so can lead to
|
||||
* use-after-frees.
|
||||
*
|
||||
* @param notifierHandle the notifier handle
|
||||
* @return the FPGA time the notifier returned
|
||||
*/
|
||||
WPI_NODISCARD
|
||||
uint64_t HAL_WaitForNotifierAlarm(HAL_NotifierHandle notifierHandle,
|
||||
int32_t* status);
|
||||
|
||||
|
||||
@@ -94,6 +94,46 @@ inline HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device,
|
||||
} // extern "C++"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Creates an int value on a simulated device.
|
||||
*
|
||||
* Returns 0 if not in simulation; this can be used to avoid calls
|
||||
* to Set/Get functions.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated value handle
|
||||
*/
|
||||
inline HAL_SimValueHandle HAL_CreateSimValueInt(HAL_SimDeviceHandle device,
|
||||
const char* name,
|
||||
int32_t direction,
|
||||
int32_t initialValue) {
|
||||
struct HAL_Value v = HAL_MakeInt(initialValue);
|
||||
return HAL_CreateSimValue(device, name, direction, &v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a long value on a simulated device.
|
||||
*
|
||||
* Returns 0 if not in simulation; this can be used to avoid calls
|
||||
* to Set/Get functions.
|
||||
*
|
||||
* @param device simulated device handle
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated value handle
|
||||
*/
|
||||
inline HAL_SimValueHandle HAL_CreateSimValueLong(HAL_SimDeviceHandle device,
|
||||
const char* name,
|
||||
int32_t direction,
|
||||
int64_t initialValue) {
|
||||
struct HAL_Value v = HAL_MakeLong(initialValue);
|
||||
return HAL_CreateSimValue(device, name, direction, &v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double value on a simulated device.
|
||||
*
|
||||
@@ -196,6 +236,30 @@ inline HAL_Value HAL_GetSimValue(HAL_SimValueHandle handle) {
|
||||
} // extern "C++"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Gets a simulated value (int).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @return The current value
|
||||
*/
|
||||
inline int32_t HAL_GetSimValueInt(HAL_SimValueHandle handle) {
|
||||
struct HAL_Value v;
|
||||
HAL_GetSimValue(handle, &v);
|
||||
return v.type == HAL_INT ? v.data.v_int : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a simulated value (long).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @return The current value
|
||||
*/
|
||||
inline int64_t HAL_GetSimValueLong(HAL_SimValueHandle handle) {
|
||||
struct HAL_Value v;
|
||||
HAL_GetSimValue(handle, &v);
|
||||
return v.type == HAL_LONG ? v.data.v_long : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a simulated value (double).
|
||||
*
|
||||
@@ -248,6 +312,28 @@ inline void HAL_SetSimValue(HAL_SimValueHandle handle, const HAL_Value& value) {
|
||||
} // extern "C++"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets a simulated value (int).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param value the value to set
|
||||
*/
|
||||
inline void HAL_SetSimValueInt(HAL_SimValueHandle handle, int value) {
|
||||
struct HAL_Value v = HAL_MakeInt(value);
|
||||
HAL_SetSimValue(handle, &v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a simulated value (long).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param value the value to set
|
||||
*/
|
||||
inline void HAL_SetSimValueLong(HAL_SimValueHandle handle, int64_t value) {
|
||||
struct HAL_Value v = HAL_MakeLong(value);
|
||||
HAL_SetSimValue(handle, &v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a simulated value (double).
|
||||
*
|
||||
@@ -281,6 +367,17 @@ inline void HAL_SetSimValueBoolean(HAL_SimValueHandle handle, HAL_Bool value) {
|
||||
HAL_SetSimValue(handle, &v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a simulated double or integral value to 0.
|
||||
* Has no effect on other value types.
|
||||
* Use this instead of Set(0) for resetting incremental sensor values like
|
||||
* encoder counts or gyro accumulated angle to ensure correct behavior in a
|
||||
* distributed system (e.g. WebSockets).
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
void HAL_ResetSimValue(HAL_SimValueHandle handle);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -342,6 +439,88 @@ class SimValue {
|
||||
HAL_SimValueHandle m_handle = HAL_kInvalidHandle;
|
||||
};
|
||||
|
||||
/**
|
||||
* C++ wrapper around a HAL simulator int value handle.
|
||||
*/
|
||||
class SimInt : public SimValue {
|
||||
public:
|
||||
/**
|
||||
* Default constructor that results in an "empty" object that is false in
|
||||
* a boolean context.
|
||||
*/
|
||||
SimInt() = default;
|
||||
|
||||
/**
|
||||
* Wraps a simulated value handle as returned by HAL_CreateSimValueInt().
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
/*implicit*/ SimInt(HAL_SimValueHandle val) // NOLINT
|
||||
: SimValue(val) {}
|
||||
|
||||
/**
|
||||
* Gets the simulated value.
|
||||
*
|
||||
* @return The current value
|
||||
*/
|
||||
int32_t Get() const { return HAL_GetSimValueInt(m_handle); }
|
||||
|
||||
/**
|
||||
* Sets the simulated value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void Set(int32_t value) { HAL_SetSimValueInt(m_handle, value); }
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting
|
||||
* incremental sensor values like encoder counts or gyro accumulated angle
|
||||
* to ensure correct behavior in a distributed system (e.g. WebSockets).
|
||||
*/
|
||||
void Reset() { HAL_ResetSimValue(m_handle); }
|
||||
};
|
||||
|
||||
/**
|
||||
* C++ wrapper around a HAL simulator long value handle.
|
||||
*/
|
||||
class SimLong : public SimValue {
|
||||
public:
|
||||
/**
|
||||
* Default constructor that results in an "empty" object that is false in
|
||||
* a boolean context.
|
||||
*/
|
||||
SimLong() = default;
|
||||
|
||||
/**
|
||||
* Wraps a simulated value handle as returned by HAL_CreateSimValueLong().
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
*/
|
||||
/*implicit*/ SimLong(HAL_SimValueHandle val) // NOLINT
|
||||
: SimValue(val) {}
|
||||
|
||||
/**
|
||||
* Gets the simulated value.
|
||||
*
|
||||
* @return The current value
|
||||
*/
|
||||
int64_t Get() const { return HAL_GetSimValueLong(m_handle); }
|
||||
|
||||
/**
|
||||
* Sets the simulated value.
|
||||
*
|
||||
* @param value the value to set
|
||||
*/
|
||||
void Set(int64_t value) { HAL_SetSimValueLong(m_handle, value); }
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting
|
||||
* incremental sensor values like encoder counts or gyro accumulated angle
|
||||
* to ensure correct behavior in a distributed system (e.g. WebSockets).
|
||||
*/
|
||||
void Reset() { HAL_ResetSimValue(m_handle); }
|
||||
};
|
||||
|
||||
/**
|
||||
* C++ wrapper around a HAL simulator double value handle.
|
||||
*/
|
||||
@@ -374,6 +553,13 @@ class SimDouble : public SimValue {
|
||||
* @param value the value to set
|
||||
*/
|
||||
void Set(double value) { HAL_SetSimValueDouble(m_handle, value); }
|
||||
|
||||
/**
|
||||
* Resets the simulated value to 0. Use this instead of Set(0) for resetting
|
||||
* incremental sensor values like encoder counts or gyro accumulated angle
|
||||
* to ensure correct behavior in a distributed system (e.g. WebSockets).
|
||||
*/
|
||||
void Reset() { HAL_ResetSimValue(m_handle); }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -562,6 +748,37 @@ class SimDevice {
|
||||
return HAL_CreateSimValue(m_handle, name, direction, &initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an int value on the simulated device.
|
||||
*
|
||||
* If not in simulation, results in an "empty" object that evaluates to false
|
||||
* in a boolean context.
|
||||
*
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated double value object
|
||||
*/
|
||||
SimInt CreateInt(const char* name, int32_t direction, int32_t initialValue) {
|
||||
return HAL_CreateSimValueInt(m_handle, name, direction, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a long value on the simulated device.
|
||||
*
|
||||
* If not in simulation, results in an "empty" object that evaluates to false
|
||||
* in a boolean context.
|
||||
*
|
||||
* @param name value name
|
||||
* @param direction input/output/bidir (from perspective of user code)
|
||||
* @param initialValue initial value
|
||||
* @return simulated double value object
|
||||
*/
|
||||
SimLong CreateLong(const char* name, int32_t direction,
|
||||
int64_t initialValue) {
|
||||
return HAL_CreateSimValueLong(m_handle, name, direction, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double value on the simulated device.
|
||||
*
|
||||
|
||||
@@ -21,11 +21,13 @@ extern "C" {
|
||||
/**
|
||||
* Gets the thread priority for the specified thread.
|
||||
*
|
||||
* @param handle Native handle pointer to the thread to get the priority for
|
||||
* @param isRealTime Set to true if thread is realtime, otherwise false
|
||||
* @param status Error status variable. 0 on success
|
||||
* @return The current thread priority. Scaled 1-99, with 1 being
|
||||
* highest.
|
||||
* @param handle Native handle pointer to the thread to get the priority
|
||||
* for.
|
||||
* @param isRealTime Set to true if thread is real-time, otherwise false.
|
||||
* @param status Error status variable. 0 on success.
|
||||
* @return The current thread priority. For real-time, this is 1-99
|
||||
* with 99 being highest. For non-real-time, this is 0. See
|
||||
* "man 7 sched" for details.
|
||||
*/
|
||||
int32_t HAL_GetThreadPriority(NativeThreadHandle handle, HAL_Bool* isRealTime,
|
||||
int32_t* status);
|
||||
@@ -34,23 +36,25 @@ int32_t HAL_GetThreadPriority(NativeThreadHandle handle, HAL_Bool* isRealTime,
|
||||
* Gets the thread priority for the current thread.
|
||||
*
|
||||
* @param handle Native handle pointer to the thread to get the priority for
|
||||
* @param isRealTime Set to true if thread is realtime, otherwise false
|
||||
* @param status Error status variable. 0 on success
|
||||
* @return The current thread priority. Scaled 1-99, with 1 being
|
||||
* highest.
|
||||
* @param isRealTime Set to true if thread is real-time, otherwise false.
|
||||
* @param status Error status variable. 0 on success.
|
||||
* @return The current thread priority. For real-time, this is 1-99
|
||||
* with 99 being highest. For non-real-time, this is 0. See
|
||||
* "man 7 sched" for details.
|
||||
*/
|
||||
int32_t HAL_GetCurrentThreadPriority(HAL_Bool* isRealTime, int32_t* status);
|
||||
|
||||
/**
|
||||
* Sets the thread priority for the specified thread.
|
||||
*
|
||||
* @param thread Reference to the thread to set the priority of
|
||||
* @param realTime Set to true to set a realtime priority, false for standard
|
||||
* priority
|
||||
* @param priority Priority to set the thread to. Scaled 1-99, with 1 being
|
||||
* highest
|
||||
* @param status Error status variable. 0 on success
|
||||
* @return The success state of setting the priority
|
||||
* @param thread Reference to the thread to set the priority of.
|
||||
* @param realTime Set to true to set a real-time priority, false for standard
|
||||
* priority.
|
||||
* @param priority Priority to set the thread to. For real-time, this is 1-99
|
||||
* with 99 being highest. For non-real-time, this is forced to
|
||||
* 0. See "man 7 sched" for more details.
|
||||
* @param status Error status variable. 0 on success.
|
||||
* @return True on success.
|
||||
*/
|
||||
HAL_Bool HAL_SetThreadPriority(NativeThreadHandle handle, HAL_Bool realTime,
|
||||
int32_t priority, int32_t* status);
|
||||
@@ -58,13 +62,14 @@ HAL_Bool HAL_SetThreadPriority(NativeThreadHandle handle, HAL_Bool realTime,
|
||||
/**
|
||||
* Sets the thread priority for the current thread.
|
||||
*
|
||||
* @param thread Reference to the thread to set the priority of
|
||||
* @param realTime Set to true to set a realtime priority, false for standard
|
||||
* priority
|
||||
* @param priority Priority to set the thread to. Scaled 1-99, with 1 being
|
||||
* highest
|
||||
* @param status Error status variable. 0 on success
|
||||
* @return The success state of setting the priority
|
||||
* @param thread Reference to the thread to set the priority of.
|
||||
* @param realTime Set to true to set a real-time priority, false for standard
|
||||
* priority.
|
||||
* @param priority Priority to set the thread to. For real-time, this is 1-99
|
||||
* with 99 being highest. For non-real-time, this is forced to
|
||||
* 0. See "man 7 sched" for more details.
|
||||
* @param status Error status variable. 0 on success.
|
||||
* @return True on success.
|
||||
*/
|
||||
HAL_Bool HAL_SetCurrentThreadPriority(HAL_Bool realTime, int32_t priority,
|
||||
int32_t* status);
|
||||
|
||||
@@ -57,6 +57,21 @@ int32_t HALSIM_RegisterSimValueChangedCallback(HAL_SimValueHandle handle,
|
||||
|
||||
void HALSIM_CancelSimValueChangedCallback(int32_t uid);
|
||||
|
||||
/**
|
||||
* Register a callback for HAL_SimValueReset(). The callback is called with
|
||||
* the old value.
|
||||
*
|
||||
* @param handle simulated value handle
|
||||
* @param callback callback
|
||||
* @param initialNotify ignored (present for consistency)
|
||||
*/
|
||||
int32_t HALSIM_RegisterSimValueResetCallback(HAL_SimValueHandle handle,
|
||||
void* param,
|
||||
HALSIM_SimValueCallback callback,
|
||||
HAL_Bool initialNotify);
|
||||
|
||||
void HALSIM_CancelSimValueResetCallback(int32_t uid);
|
||||
|
||||
HAL_SimValueHandle HALSIM_GetSimValueHandle(HAL_SimDeviceHandle device,
|
||||
const char* name);
|
||||
|
||||
|
||||
@@ -174,6 +174,11 @@ HAL_NotifierHandle HAL_InitializeNotifier(int32_t* status) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
HAL_Bool HAL_SetNotifierThreadPriority(HAL_Bool realTime, int32_t priority,
|
||||
int32_t* status) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void HAL_SetNotifierName(HAL_NotifierHandle notifierHandle, const char* name,
|
||||
int32_t* status) {
|
||||
auto notifier = notifierHandles->Get(notifierHandle);
|
||||
|
||||
@@ -61,6 +61,10 @@ void HAL_SetSimValue(HAL_SimValueHandle handle, const struct HAL_Value* value) {
|
||||
SimSimDeviceData->SetValue(handle, *value);
|
||||
}
|
||||
|
||||
void HAL_ResetSimValue(HAL_SimValueHandle handle) {
|
||||
SimSimDeviceData->ResetValue(handle);
|
||||
}
|
||||
|
||||
hal::SimDevice::SimDevice(const char* name, int index) {
|
||||
wpi::SmallString<128> fullname;
|
||||
wpi::raw_svector_ostream os(fullname);
|
||||
|
||||
@@ -220,6 +220,47 @@ void SimDeviceData::SetValue(HAL_SimValueHandle handle,
|
||||
valueImpl->direction, &value);
|
||||
}
|
||||
|
||||
void SimDeviceData::ResetValue(HAL_SimValueHandle handle) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
Value* valueImpl = LookupValue(handle);
|
||||
if (!valueImpl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't notify reset if we aren't going to actually reset anything
|
||||
switch (valueImpl->value.type) {
|
||||
case HAL_INT:
|
||||
case HAL_LONG:
|
||||
case HAL_DOUBLE:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// notify reset callbacks (done here so they're called with the old value)
|
||||
valueImpl->reset(valueImpl->name.c_str(), valueImpl->handle,
|
||||
valueImpl->direction, &valueImpl->value);
|
||||
|
||||
// set user-facing value to 0
|
||||
switch (valueImpl->value.type) {
|
||||
case HAL_INT:
|
||||
valueImpl->value.data.v_int = 0;
|
||||
break;
|
||||
case HAL_LONG:
|
||||
valueImpl->value.data.v_long = 0;
|
||||
break;
|
||||
case HAL_DOUBLE:
|
||||
valueImpl->value.data.v_double = 0;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// notify changed callbacks
|
||||
valueImpl->changed(valueImpl->name.c_str(), valueImpl->handle,
|
||||
valueImpl->direction, &valueImpl->value);
|
||||
}
|
||||
|
||||
int32_t SimDeviceData::RegisterDeviceCreatedCallback(
|
||||
const char* prefix, void* param, HALSIM_SimDeviceCallback callback,
|
||||
bool initialNotify) {
|
||||
@@ -368,6 +409,35 @@ void SimDeviceData::CancelValueChangedCallback(int32_t uid) {
|
||||
valueImpl->changed.Cancel(uid & 0x7f);
|
||||
}
|
||||
|
||||
int32_t SimDeviceData::RegisterValueResetCallback(
|
||||
HAL_SimValueHandle handle, void* param, HALSIM_SimValueCallback callback,
|
||||
bool initialNotify) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
Value* valueImpl = LookupValue(handle);
|
||||
if (!valueImpl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// register callback
|
||||
int32_t index = valueImpl->reset.Register(callback, param);
|
||||
|
||||
// encode device and value into uid
|
||||
return (((handle >> 16) & 0xfff) << 19) | ((handle & 0xfff) << 7) |
|
||||
(index & 0x7f);
|
||||
}
|
||||
|
||||
void SimDeviceData::CancelValueResetCallback(int32_t uid) {
|
||||
if (uid <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(m_mutex);
|
||||
Value* valueImpl = LookupValue(((uid >> 19) << 16) | ((uid >> 7) & 0xfff));
|
||||
if (!valueImpl) {
|
||||
return;
|
||||
}
|
||||
valueImpl->reset.Cancel(uid & 0x7f);
|
||||
}
|
||||
|
||||
HAL_SimValueHandle SimDeviceData::GetValueHandle(HAL_SimDeviceHandle device,
|
||||
const char* name) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
@@ -517,6 +587,18 @@ void HALSIM_CancelSimValueChangedCallback(int32_t uid) {
|
||||
SimSimDeviceData->CancelValueChangedCallback(uid);
|
||||
}
|
||||
|
||||
int32_t HALSIM_RegisterSimValueResetCallback(HAL_SimValueHandle handle,
|
||||
void* param,
|
||||
HALSIM_SimValueCallback callback,
|
||||
HAL_Bool initialNotify) {
|
||||
return SimSimDeviceData->RegisterValueResetCallback(handle, param, callback,
|
||||
initialNotify);
|
||||
}
|
||||
|
||||
void HALSIM_CancelSimValueResetCallback(int32_t uid) {
|
||||
SimSimDeviceData->CancelValueResetCallback(uid);
|
||||
}
|
||||
|
||||
HAL_SimValueHandle HALSIM_GetSimValueHandle(HAL_SimDeviceHandle device,
|
||||
const char* name) {
|
||||
return SimSimDeviceData->GetValueHandle(device, name);
|
||||
|
||||
@@ -150,6 +150,7 @@ class SimDeviceData {
|
||||
std::vector<const char*> cstrEnumOptions;
|
||||
std::vector<double> enumOptionValues;
|
||||
impl::SimUnnamedCallbackRegistry<HALSIM_SimValueCallback> changed;
|
||||
impl::SimUnnamedCallbackRegistry<HALSIM_SimValueCallback> reset;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
@@ -188,6 +189,7 @@ class SimDeviceData {
|
||||
const HAL_Value& initialValue);
|
||||
HAL_Value GetValue(HAL_SimValueHandle handle);
|
||||
void SetValue(HAL_SimValueHandle handle, const HAL_Value& value);
|
||||
void ResetValue(HAL_SimValueHandle handle);
|
||||
|
||||
int32_t RegisterDeviceCreatedCallback(const char* prefix, void* param,
|
||||
HALSIM_SimDeviceCallback callback,
|
||||
@@ -218,6 +220,12 @@ class SimDeviceData {
|
||||
|
||||
void CancelValueChangedCallback(int32_t uid);
|
||||
|
||||
int32_t RegisterValueResetCallback(HAL_SimValueHandle handle, void* param,
|
||||
HALSIM_SimValueCallback callback,
|
||||
bool initialNotify);
|
||||
|
||||
void CancelValueResetCallback(int32_t uid);
|
||||
|
||||
HAL_SimValueHandle GetValueHandle(HAL_SimDeviceHandle device,
|
||||
const char* name);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ project(imgui-download NONE)
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(glfw3
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 63af05c41961c238c2f891e2c923e1b89c1271b6
|
||||
GIT_TAG 2a5ac9a6d6cbe9f4113c0f17158cb13ab7f263bf
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/glfw-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/glfw-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
@@ -23,7 +23,7 @@ ExternalProject_Add(gl3w
|
||||
)
|
||||
ExternalProject_Add(imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG v1.76
|
||||
GIT_TAG v1.79
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/imgui-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/imgui-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
@@ -33,7 +33,7 @@ ExternalProject_Add(imgui
|
||||
)
|
||||
ExternalProject_Add(implot
|
||||
GIT_REPOSITORY https://github.com/epezent/implot.git
|
||||
GIT_TAG 90693cca1bd0ca5f0d49bc9cb8187d56b0b8f289
|
||||
GIT_TAG a6bab98517b1baa3116db52518dda1eb2d7eaab7
|
||||
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-src"
|
||||
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/implot-build"
|
||||
CONFIGURE_COMMAND ""
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#ifndef NTCORE_CONNECTIONNOTIFIER_H_
|
||||
#define NTCORE_CONNECTIONNOTIFIER_H_
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include <wpi/CallbackManager.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "IConnectionNotifier.h"
|
||||
#include "ntcore_cpp.h"
|
||||
@@ -15,7 +16,8 @@ namespace nt {
|
||||
namespace impl {
|
||||
|
||||
class ConnectionNotifierThread
|
||||
: public CallbackThread<ConnectionNotifierThread, ConnectionNotification> {
|
||||
: public wpi::CallbackThread<ConnectionNotifierThread,
|
||||
ConnectionNotification> {
|
||||
public:
|
||||
explicit ConnectionNotifierThread(int inst) : m_inst(inst) {}
|
||||
|
||||
@@ -42,11 +44,11 @@ class ConnectionNotifierThread
|
||||
|
||||
class ConnectionNotifier
|
||||
: public IConnectionNotifier,
|
||||
public CallbackManager<ConnectionNotifier,
|
||||
impl::ConnectionNotifierThread> {
|
||||
public wpi::CallbackManager<ConnectionNotifier,
|
||||
impl::ConnectionNotifierThread> {
|
||||
friend class ConnectionNotifierTest;
|
||||
friend class CallbackManager<ConnectionNotifier,
|
||||
impl::ConnectionNotifierThread>;
|
||||
friend class wpi::CallbackManager<ConnectionNotifier,
|
||||
impl::ConnectionNotifierThread>;
|
||||
|
||||
public:
|
||||
explicit ConnectionNotifier(int inst);
|
||||
|
||||
@@ -112,7 +112,7 @@ class DispatcherBase : public IDispatcher {
|
||||
// Condition variable for forced dispatch wakeup (flush)
|
||||
wpi::mutex m_flush_mutex;
|
||||
wpi::condition_variable m_flush_cv;
|
||||
uint64_t m_last_flush;
|
||||
uint64_t m_last_flush = 0;
|
||||
bool m_do_flush = false;
|
||||
|
||||
// Condition variable for client reconnect (uses user mutex)
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include <wpi/CallbackManager.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "IEntryNotifier.h"
|
||||
#include "ntcore_cpp.h"
|
||||
@@ -23,22 +24,23 @@ namespace nt {
|
||||
namespace impl {
|
||||
|
||||
struct EntryListenerData
|
||||
: public ListenerData<std::function<void(const EntryNotification& event)>> {
|
||||
: public wpi::CallbackListenerData<
|
||||
std::function<void(const EntryNotification& event)>> {
|
||||
EntryListenerData() = default;
|
||||
EntryListenerData(
|
||||
std::function<void(const EntryNotification& event)> callback_,
|
||||
StringRef prefix_, unsigned int flags_)
|
||||
: ListenerData(callback_), prefix(prefix_), flags(flags_) {}
|
||||
: CallbackListenerData(callback_), prefix(prefix_), flags(flags_) {}
|
||||
EntryListenerData(
|
||||
std::function<void(const EntryNotification& event)> callback_,
|
||||
NT_Entry entry_, unsigned int flags_)
|
||||
: ListenerData(callback_), entry(entry_), flags(flags_) {}
|
||||
: CallbackListenerData(callback_), entry(entry_), flags(flags_) {}
|
||||
EntryListenerData(unsigned int poller_uid_, StringRef prefix_,
|
||||
unsigned int flags_)
|
||||
: ListenerData(poller_uid_), prefix(prefix_), flags(flags_) {}
|
||||
: CallbackListenerData(poller_uid_), prefix(prefix_), flags(flags_) {}
|
||||
EntryListenerData(unsigned int poller_uid_, NT_Entry entry_,
|
||||
unsigned int flags_)
|
||||
: ListenerData(poller_uid_), entry(entry_), flags(flags_) {}
|
||||
: CallbackListenerData(poller_uid_), entry(entry_), flags(flags_) {}
|
||||
|
||||
std::string prefix;
|
||||
NT_Entry entry = 0;
|
||||
@@ -46,8 +48,8 @@ struct EntryListenerData
|
||||
};
|
||||
|
||||
class EntryNotifierThread
|
||||
: public CallbackThread<EntryNotifierThread, EntryNotification,
|
||||
EntryListenerData> {
|
||||
: public wpi::CallbackThread<EntryNotifierThread, EntryNotification,
|
||||
EntryListenerData> {
|
||||
public:
|
||||
explicit EntryNotifierThread(int inst) : m_inst(inst) {}
|
||||
|
||||
@@ -71,9 +73,9 @@ class EntryNotifierThread
|
||||
|
||||
class EntryNotifier
|
||||
: public IEntryNotifier,
|
||||
public CallbackManager<EntryNotifier, impl::EntryNotifierThread> {
|
||||
public wpi::CallbackManager<EntryNotifier, impl::EntryNotifierThread> {
|
||||
friend class EntryNotifierTest;
|
||||
friend class CallbackManager<EntryNotifier, impl::EntryNotifierThread>;
|
||||
friend class wpi::CallbackManager<EntryNotifier, impl::EntryNotifierThread>;
|
||||
|
||||
public:
|
||||
explicit EntryNotifier(int inst, wpi::Logger& logger);
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#ifndef NTCORE_LOGGERIMPL_H_
|
||||
#define NTCORE_LOGGERIMPL_H_
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include <wpi/CallbackManager.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
@@ -13,15 +14,17 @@ namespace nt {
|
||||
|
||||
namespace impl {
|
||||
|
||||
struct LoggerListenerData
|
||||
: public ListenerData<std::function<void(const LogMessage& msg)>> {
|
||||
struct LoggerListenerData : public wpi::CallbackListenerData<
|
||||
std::function<void(const LogMessage& msg)>> {
|
||||
LoggerListenerData() = default;
|
||||
LoggerListenerData(std::function<void(const LogMessage& msg)> callback_,
|
||||
unsigned int min_level_, unsigned int max_level_)
|
||||
: ListenerData(callback_), min_level(min_level_), max_level(max_level_) {}
|
||||
: CallbackListenerData(callback_),
|
||||
min_level(min_level_),
|
||||
max_level(max_level_) {}
|
||||
LoggerListenerData(unsigned int poller_uid_, unsigned int min_level_,
|
||||
unsigned int max_level_)
|
||||
: ListenerData(poller_uid_),
|
||||
: CallbackListenerData(poller_uid_),
|
||||
min_level(min_level_),
|
||||
max_level(max_level_) {}
|
||||
|
||||
@@ -30,7 +33,7 @@ struct LoggerListenerData
|
||||
};
|
||||
|
||||
class LoggerThread
|
||||
: public CallbackThread<LoggerThread, LogMessage, LoggerListenerData> {
|
||||
: public wpi::CallbackThread<LoggerThread, LogMessage, LoggerListenerData> {
|
||||
public:
|
||||
explicit LoggerThread(int inst) : m_inst(inst) {}
|
||||
|
||||
@@ -52,9 +55,9 @@ class LoggerThread
|
||||
|
||||
} // namespace impl
|
||||
|
||||
class LoggerImpl : public CallbackManager<LoggerImpl, impl::LoggerThread> {
|
||||
class LoggerImpl : public wpi::CallbackManager<LoggerImpl, impl::LoggerThread> {
|
||||
friend class LoggerTest;
|
||||
friend class CallbackManager<LoggerImpl, impl::LoggerThread>;
|
||||
friend class wpi::CallbackManager<LoggerImpl, impl::LoggerThread>;
|
||||
|
||||
public:
|
||||
explicit LoggerImpl(int inst);
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <wpi/CallbackManager.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/mutex.h>
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include "Handle.h"
|
||||
#include "IRpcServer.h"
|
||||
#include "Log.h"
|
||||
@@ -32,11 +32,11 @@ struct RpcNotifierData : public RpcAnswer {
|
||||
};
|
||||
|
||||
using RpcListenerData =
|
||||
ListenerData<std::function<void(const RpcAnswer& answer)>>;
|
||||
wpi::CallbackListenerData<std::function<void(const RpcAnswer& answer)>>;
|
||||
|
||||
class RpcServerThread
|
||||
: public CallbackThread<RpcServerThread, RpcAnswer, RpcListenerData,
|
||||
RpcNotifierData> {
|
||||
: public wpi::CallbackThread<RpcServerThread, RpcAnswer, RpcListenerData,
|
||||
RpcNotifierData> {
|
||||
public:
|
||||
RpcServerThread(int inst, wpi::Logger& logger)
|
||||
: m_inst(inst), m_logger(logger) {}
|
||||
@@ -78,10 +78,11 @@ class RpcServerThread
|
||||
|
||||
} // namespace impl
|
||||
|
||||
class RpcServer : public IRpcServer,
|
||||
public CallbackManager<RpcServer, impl::RpcServerThread> {
|
||||
class RpcServer
|
||||
: public IRpcServer,
|
||||
public wpi::CallbackManager<RpcServer, impl::RpcServerThread> {
|
||||
friend class RpcServerTest;
|
||||
friend class CallbackManager<RpcServer, impl::RpcServerThread>;
|
||||
friend class wpi::CallbackManager<RpcServer, impl::RpcServerThread>;
|
||||
|
||||
public:
|
||||
RpcServer(int inst, wpi::Logger& logger);
|
||||
|
||||
@@ -11,7 +11,7 @@ nativeUtils {
|
||||
niLibVersion = "2020.10.1"
|
||||
opencvVersion = "3.4.7-5"
|
||||
googleTestVersion = "1.9.0-5-437e100-1"
|
||||
imguiVersion = "1.76-10"
|
||||
imguiVersion = "1.79-2"
|
||||
wpimathVersion = "-1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,14 @@ task cppHeadersZip(type: Zip) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from('src/main/native/include') {
|
||||
into '/'
|
||||
ext.includeDirs = [
|
||||
project.file('src/main/native/include')
|
||||
]
|
||||
|
||||
ext.includeDirs.each {
|
||||
from(it) {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,14 @@ task cppHeadersZip(type: Zip) {
|
||||
into '/'
|
||||
}
|
||||
|
||||
from('src/main/native/include') {
|
||||
into '/'
|
||||
ext.includeDirs = [
|
||||
project.file('src/main/native/include')
|
||||
]
|
||||
|
||||
ext.includeDirs.each {
|
||||
from(it) {
|
||||
into '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,18 +43,7 @@ addTaskToCopyAllOutputs(cppHeadersZip)
|
||||
|
||||
model {
|
||||
publishing {
|
||||
def pluginTaskList = createComponentZipTasks($.components, [pluginName], zipBaseName, Zip, project, { task, value ->
|
||||
value.each { binary ->
|
||||
if (binary.buildable) {
|
||||
if (binary instanceof SharedLibraryBinarySpec) {
|
||||
task.dependsOn binary.buildTask
|
||||
task.from(binary.sharedLibraryFile) {
|
||||
into nativeUtils.getPlatformPath(binary) + '/shared'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
def pluginTaskList = createComponentZipTasks($.components, [pluginName], zipBaseName, Zip, project, includeStandardZipFormat)
|
||||
|
||||
publications {
|
||||
cpp(MavenPublication) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <glass/support/ExtraGuiWidgets.h>
|
||||
#include <glass/support/IniSaverInfo.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -122,6 +123,7 @@ class KeyboardJoystick : public SystemJoystick {
|
||||
int decKey = -1;
|
||||
float keyRate = 0.05f;
|
||||
float decayRate = 0.05f;
|
||||
float maxAbsValue = 1.0f;
|
||||
};
|
||||
AxisConfig m_axisConfig[HAL_kMaxJoystickAxes];
|
||||
|
||||
@@ -712,6 +714,12 @@ void KeyboardJoystick::SettingsDisplay() {
|
||||
EditKey("Decrease", &m_axisConfig[i].decKey);
|
||||
ImGui::InputFloat("Key Rate", &m_axisConfig[i].keyRate);
|
||||
ImGui::InputFloat("Decay Rate", &m_axisConfig[i].decayRate);
|
||||
|
||||
float maxAbsValue = m_axisConfig[i].maxAbsValue;
|
||||
if (ImGui::InputFloat("Max Absolute Value", &maxAbsValue)) {
|
||||
m_axisConfig[i].maxAbsValue = std::clamp(maxAbsValue, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -771,6 +779,10 @@ void KeyboardJoystick::SettingsDisplay() {
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
static inline bool IsKeyDown(ImGuiIO& io, int key) {
|
||||
return key >= 0 && key < IM_ARRAYSIZE(ImGuiIO::KeysDown) && io.KeysDown[key];
|
||||
}
|
||||
|
||||
void KeyboardJoystick::Update() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
@@ -784,10 +796,10 @@ void KeyboardJoystick::Update() {
|
||||
auto& config = m_axisConfig[i];
|
||||
float& axisValue = m_data.axes.axes[i];
|
||||
// increase/decrease while key held down (to saturation); decay back to 0
|
||||
if (config.incKey >= 0 && io.KeysDown[config.incKey]) {
|
||||
if (IsKeyDown(io, config.incKey)) {
|
||||
axisValue += config.keyRate;
|
||||
if (axisValue > 1.0) {
|
||||
axisValue = 1.0;
|
||||
if (axisValue > config.maxAbsValue) {
|
||||
axisValue = config.maxAbsValue;
|
||||
}
|
||||
} else if (axisValue > 0) {
|
||||
if (axisValue < config.decayRate) {
|
||||
@@ -797,10 +809,10 @@ void KeyboardJoystick::Update() {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.decKey >= 0 && io.KeysDown[config.decKey]) {
|
||||
if (IsKeyDown(io, config.decKey)) {
|
||||
axisValue -= config.keyRate;
|
||||
if (axisValue < -1.0) {
|
||||
axisValue = -1.0;
|
||||
if (axisValue < -config.maxAbsValue) {
|
||||
axisValue = -config.maxAbsValue;
|
||||
}
|
||||
} else if (axisValue < 0) {
|
||||
if (axisValue > -config.decayRate) {
|
||||
@@ -815,7 +827,7 @@ void KeyboardJoystick::Update() {
|
||||
m_data.buttons.buttons = 0;
|
||||
m_anyButtonPressed = false;
|
||||
for (int i = 0; i < m_data.buttons.count; ++i) {
|
||||
if (m_buttonKey[i] >= 0 && io.KeysDown[m_buttonKey[i]]) {
|
||||
if (IsKeyDown(io, m_buttonKey[i])) {
|
||||
m_data.buttons.buttons |= 1u << i;
|
||||
m_anyButtonPressed = true;
|
||||
}
|
||||
@@ -826,21 +838,21 @@ void KeyboardJoystick::Update() {
|
||||
auto& config = m_povConfig[i];
|
||||
auto& povValue = m_data.povs.povs[i];
|
||||
povValue = -1;
|
||||
if (config.key0 >= 0 && io.KeysDown[config.key0]) {
|
||||
if (IsKeyDown(io, config.key0)) {
|
||||
povValue = 0;
|
||||
} else if (config.key45 >= 0 && io.KeysDown[config.key45]) {
|
||||
} else if (IsKeyDown(io, config.key45)) {
|
||||
povValue = 45;
|
||||
} else if (config.key90 >= 0 && io.KeysDown[config.key90]) {
|
||||
} else if (IsKeyDown(io, config.key90)) {
|
||||
povValue = 90;
|
||||
} else if (config.key135 >= 0 && io.KeysDown[config.key135]) {
|
||||
} else if (IsKeyDown(io, config.key135)) {
|
||||
povValue = 135;
|
||||
} else if (config.key180 >= 0 && io.KeysDown[config.key180]) {
|
||||
} else if (IsKeyDown(io, config.key180)) {
|
||||
povValue = 180;
|
||||
} else if (config.key225 >= 0 && io.KeysDown[config.key225]) {
|
||||
} else if (IsKeyDown(io, config.key225)) {
|
||||
povValue = 225;
|
||||
} else if (config.key270 >= 0 && io.KeysDown[config.key270]) {
|
||||
} else if (IsKeyDown(io, config.key270)) {
|
||||
povValue = 270;
|
||||
} else if (config.key315 >= 0 && io.KeysDown[config.key315]) {
|
||||
} else if (IsKeyDown(io, config.key315)) {
|
||||
povValue = 315;
|
||||
}
|
||||
}
|
||||
@@ -937,6 +949,8 @@ void KeyboardJoystick::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
std::sscanf(value.data(), "%f", &m_axisConfig[index].keyRate);
|
||||
} else if (name == "decayRate") {
|
||||
std::sscanf(value.data(), "%f", &m_axisConfig[index].decayRate);
|
||||
} else if (name == "maxAbsValue") {
|
||||
std::sscanf(value.data(), "%f", &m_axisConfig[index].maxAbsValue);
|
||||
}
|
||||
} else if (name.startswith("button")) {
|
||||
name = name.drop_front(6);
|
||||
@@ -960,6 +974,9 @@ void KeyboardJoystick::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (value.getAsInteger(10, v)) {
|
||||
return;
|
||||
}
|
||||
if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
|
||||
return;
|
||||
}
|
||||
m_buttonKey[index] = v;
|
||||
} else if (name.startswith("pov")) {
|
||||
name = name.drop_front(3);
|
||||
@@ -983,6 +1000,9 @@ void KeyboardJoystick::ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (value.getAsInteger(10, v)) {
|
||||
return;
|
||||
}
|
||||
if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
|
||||
return;
|
||||
}
|
||||
if (name == "key0") {
|
||||
m_povConfig[index].key0 = v;
|
||||
} else if (name == "key45") {
|
||||
@@ -1010,8 +1030,9 @@ void KeyboardJoystick::WriteIni(ImGuiTextBuffer* out_buf) const {
|
||||
auto& c = m_axisConfig[i];
|
||||
out_buf->appendf(
|
||||
"axis%dincKey=%d\naxis%ddecKey=%d\naxis%dkeyRate=%f\n"
|
||||
"axis%ddecayRate=%f\n",
|
||||
i, c.incKey, i, c.decKey, i, c.keyRate, i, c.decayRate);
|
||||
"axis%ddecayRate=%f\naxis%dmaxAbsValue=%f\n",
|
||||
i, c.incKey, i, c.decKey, i, c.keyRate, i, c.decayRate, i,
|
||||
c.maxAbsValue);
|
||||
}
|
||||
for (int i = 0; i < m_data.buttons.count; ++i) {
|
||||
out_buf->appendf("button%d=%d\n", i, m_buttonKey[i]);
|
||||
@@ -1337,7 +1358,19 @@ static void DisplaySystemJoysticks() {
|
||||
DisplaySystemJoystick(*gGlfwJoysticks[i], i);
|
||||
}
|
||||
for (size_t i = 0; i < gKeyboardJoysticks.size(); ++i) {
|
||||
DisplaySystemJoystick(*gKeyboardJoysticks[i], i + GLFW_JOYSTICK_LAST + 1);
|
||||
auto joy = gKeyboardJoysticks[i].get();
|
||||
DisplaySystemJoystick(*joy, i + GLFW_JOYSTICK_LAST + 1);
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), "%s Settings", joy->GetName());
|
||||
if (ImGui::MenuItem(buf)) {
|
||||
if (auto win = DriverStationGui::dsManager.GetWindow(buf)) {
|
||||
win->SetVisible(true);
|
||||
}
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class SimDevicesModel : public glass::Model {
|
||||
} // namespace
|
||||
|
||||
static SimDevicesModel* gSimDevicesModel;
|
||||
static bool gSimDevicesShowPrefix = false;
|
||||
|
||||
void SimDevicesModel::Update() {
|
||||
HALSIM_EnumerateSimDevices(
|
||||
@@ -136,10 +137,14 @@ static void DisplaySimValue(const char* name, void* data,
|
||||
|
||||
static void DisplaySimDevice(const char* name, void* data,
|
||||
HAL_SimDeviceHandle handle) {
|
||||
// only show "Foo" portion of "Accel:Foo"
|
||||
auto [type, id] = wpi::StringRef{name}.split(':');
|
||||
if (id.empty()) {
|
||||
id = type;
|
||||
wpi::StringRef id{name};
|
||||
if (!gSimDevicesShowPrefix) {
|
||||
// only show "Foo" portion of "Accel:Foo"
|
||||
wpi::StringRef type;
|
||||
std::tie(type, id) = id.split(':');
|
||||
if (id.empty()) {
|
||||
id = type;
|
||||
}
|
||||
}
|
||||
if (glass::BeginDevice(id.data())) {
|
||||
HALSIM_EnumerateSimValues(handle, data, DisplaySimValue);
|
||||
@@ -154,8 +159,14 @@ void SimDeviceGui::Initialize() {
|
||||
[](glass::Window* win, glass::Model* model) {
|
||||
win->SetDefaultPos(1025, 20);
|
||||
win->SetDefaultSize(250, 695);
|
||||
return glass::MakeFunctionView(
|
||||
[=] { static_cast<glass::DeviceTreeModel*>(model)->Display(); });
|
||||
win->DisableRenamePopup();
|
||||
return glass::MakeFunctionView([=] {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
ImGui::Checkbox("Show prefix", &gSimDevicesShowPrefix);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
static_cast<glass::DeviceTreeModel*>(model)->Display();
|
||||
});
|
||||
});
|
||||
HALSimGui::halProvider.ShowDefault("Other Devices");
|
||||
|
||||
|
||||
516
simulation/halsim_ws_core/doc/wpilib-ws.yaml
Normal file
516
simulation/halsim_ws_core/doc/wpilib-ws.yaml
Normal file
@@ -0,0 +1,516 @@
|
||||
asyncapi: 2.0.0
|
||||
info:
|
||||
title: WPILib WebSocket Remote Endpoint API
|
||||
version: "1.0.0"
|
||||
description: |
|
||||
API to route WPILib HAL calls over WebSockets.
|
||||
license:
|
||||
name: WPILib BSD
|
||||
|
||||
channels:
|
||||
wpilibws:
|
||||
description: General channel for WPILib WebSocket messages
|
||||
publish:
|
||||
operationId: wpilibwsPublish
|
||||
message:
|
||||
$ref: "#/components/messages/wpilibwsMsg"
|
||||
subscribe:
|
||||
operationId: wpilibwsSubscribe
|
||||
message:
|
||||
$ref: "#/components/messages/wpilibwsMsg"
|
||||
|
||||
components:
|
||||
messages:
|
||||
wpilibwsMsg:
|
||||
title: WPILib WebSocket Message
|
||||
summary: Message envelope. Note that the "data" field contains a diff of the current state of a particular device. E.g. If only the "value" changes for a DIO device, then only the "<>value" field will be sent.
|
||||
contentType: application/json
|
||||
examples:
|
||||
- payload:
|
||||
type: PWM
|
||||
device: "1"
|
||||
data:
|
||||
"<speed": 0.5
|
||||
- payload:
|
||||
type: DIO
|
||||
device: "3"
|
||||
data:
|
||||
"<init": true
|
||||
payload:
|
||||
type: object
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/accelData"
|
||||
- $ref: "#/components/schemas/aiData"
|
||||
- $ref: "#/components/schemas/dioData"
|
||||
- $ref: "#/components/schemas/dpwmData"
|
||||
- $ref: "#/components/schemas/driverstationData"
|
||||
- $ref: "#/components/schemas/dutycycleData"
|
||||
- $ref: "#/components/schemas/encoderData"
|
||||
- $ref: "#/components/schemas/gyroData"
|
||||
- $ref: "#/components/schemas/joystickData"
|
||||
- $ref: "#/components/schemas/pwmData"
|
||||
- $ref: "#/components/schemas/relayData"
|
||||
- $ref: "#/components/schemas/solenoidData"
|
||||
- $ref: "#/components/schemas/roborioData"
|
||||
|
||||
schemas:
|
||||
accelData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Accel
|
||||
device:
|
||||
type: string
|
||||
description: Arbitrary device name
|
||||
data:
|
||||
type: object
|
||||
description: "Accelerometer Data (type: Accelerometer, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If accelerometer is initialized in the robot program"
|
||||
<range:
|
||||
type: number
|
||||
description: "Desired range in G’s"
|
||||
">x":
|
||||
type: number
|
||||
description: "Acceleration in G’s "
|
||||
">y":
|
||||
type: number
|
||||
description: "Acceleration in G’s "
|
||||
">z":
|
||||
type: number
|
||||
description: "Acceleration in G’s "
|
||||
|
||||
aiData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: AI
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Analog Input Data (type: AI, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If analog input is initialized in the robot program"
|
||||
">voltage":
|
||||
type: number
|
||||
description: "Input voltage, in volts"
|
||||
|
||||
dioData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: DIO
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Digital Input/Output Data (type: DIO, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If DIO is initialized in the robot program"
|
||||
<input:
|
||||
type: boolean
|
||||
description: "True if input, false if output"
|
||||
<>value:
|
||||
type: boolean
|
||||
description: "Input or output state"
|
||||
|
||||
dpwmData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: dPWM
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Duty Cycle Output Data (type: dPWM, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If output is initialized in the robot program"
|
||||
<duty_cycle:
|
||||
type: number
|
||||
description: "Duty cycle %"
|
||||
minimum: 0.0
|
||||
maximum: 1.0
|
||||
<dio_pin:
|
||||
type: integer
|
||||
description: "DIO pin number"
|
||||
|
||||
driverstationData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: DriverStation
|
||||
device:
|
||||
type: string
|
||||
description: Should be left blank
|
||||
data:
|
||||
type: object
|
||||
description: "Driver Station Data (type: DriverStation)"
|
||||
properties:
|
||||
">new_data":
|
||||
type: boolean
|
||||
description: "One shot. If set to true in a message, notifies the robot program that new DS and Joystick data is available."
|
||||
">enabled":
|
||||
type: boolean
|
||||
description: "True to enable the robot program"
|
||||
">autonomous":
|
||||
type: boolean
|
||||
description: "True for autonomous mode; false for teleoperated mode"
|
||||
">test":
|
||||
type: boolean
|
||||
description: "True for test mode; false for other modes"
|
||||
">estop":
|
||||
type: boolean
|
||||
description: "True to emergency stop (no motor outputs)"
|
||||
">fms":
|
||||
type: boolean
|
||||
description: "True if the DS is connected to a Field Management System (FMS)"
|
||||
">ds":
|
||||
type: boolean
|
||||
description: "True if a DS application is connected"
|
||||
">station":
|
||||
type: string
|
||||
description: "Station color and number; supported values are 'red1', 'red2', 'red3', 'blue1', 'blue2', 'blue3'."
|
||||
">match_time":
|
||||
type: number
|
||||
description: "Match time countdown, in seconds, for each match period (e.g. for 15 second period, starts at 15 and counts down to 0). If not in a match, -1."
|
||||
">game_data":
|
||||
type: string
|
||||
description: "Game-specific data; arbitrary string contents"
|
||||
|
||||
dutycycleData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: DutyCycle
|
||||
device:
|
||||
type: string
|
||||
description: Arbitrary device name
|
||||
data:
|
||||
type: object
|
||||
description: "Duty Cycle Input Data (type: DutyCycle, device: channel number)"
|
||||
properties:
|
||||
">connected":
|
||||
type: boolean
|
||||
description: "True if the encoder is connected"
|
||||
">position":
|
||||
type: number
|
||||
description: "The position in rotations"
|
||||
|
||||
encoderData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Encoder
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Quadrature Encoder Data (type: Encoder, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If encoder is initialized in the robot program"
|
||||
<channel_a:
|
||||
type: integer
|
||||
description: "Digital channel number for 'A' phase"
|
||||
<channel_b:
|
||||
type: integer
|
||||
description: "Digital channel number for 'B' phase"
|
||||
<samples_to_avg:
|
||||
type: integer
|
||||
description: "Number of samples to average for period measurement"
|
||||
">count":
|
||||
type: integer
|
||||
description: "Accumulated count (pulses)"
|
||||
">period":
|
||||
type: number
|
||||
description: "Period between pulses in seconds"
|
||||
|
||||
gyroData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Gyro
|
||||
device:
|
||||
type: string
|
||||
description: Arbitrary device name
|
||||
data:
|
||||
type: object
|
||||
description: "Gyro Data (type: Gyro, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If gyro is initialized in the robot program"
|
||||
<range:
|
||||
type: number
|
||||
description: "Gyro range in degrees/second (optional)"
|
||||
">connected":
|
||||
type: boolean
|
||||
description: "True if the gyro is connected"
|
||||
">angle_x":
|
||||
type: number
|
||||
description: "The gyro angle in degrees"
|
||||
">angle_y":
|
||||
type: number
|
||||
description: "The gyro angle in degrees"
|
||||
">angle_z":
|
||||
type: number
|
||||
description: "The gyro angle in degrees"
|
||||
">rate_x":
|
||||
type: number
|
||||
description: "The current gyro angular rate of change in degrees/second"
|
||||
">rate_y":
|
||||
type: number
|
||||
description: "The current gyro angular rate of change in degrees/second"
|
||||
">rate_z":
|
||||
type: number
|
||||
description: "The current gyro angular rate of change in degrees/second"
|
||||
|
||||
joystickData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Joystick
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Joystick Data (type: Joystick, device: channel number)"
|
||||
properties:
|
||||
">axes":
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
description: "Value of an individual axis on this joystick"
|
||||
minimum: -1.0
|
||||
maximum: 1.0
|
||||
">povs":
|
||||
type: array
|
||||
description: "One array element per POV; value is a"
|
||||
items:
|
||||
type: integer
|
||||
description: "State of all POV switches on this joystick; an angle in degrees of the POV (e.g. 0, 90, 315) if pressed, or -1 if the POV is not pressed"
|
||||
">buttons":
|
||||
type: array
|
||||
description: State of all buttons on this joystick
|
||||
items:
|
||||
type: boolean
|
||||
description: Pressed state of an individual button
|
||||
<rumble_left:
|
||||
type: number
|
||||
description: "Left rumble"
|
||||
minimum: 0.0
|
||||
maximum: 1.0
|
||||
<rumble_right:
|
||||
type: number
|
||||
description: "Right rumble"
|
||||
minimum: 0.0
|
||||
maximum: 1.0
|
||||
|
||||
pwmData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: PWM
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "PWM Output Data (type: PWM, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If PWM is initialized in the robot program"
|
||||
<speed:
|
||||
type: number
|
||||
description: "Speed"
|
||||
minimum: -1.0
|
||||
maximum: 1.0
|
||||
<position:
|
||||
type: number
|
||||
description: "Servo position"
|
||||
minimum: 0.0
|
||||
maximum: 1.0
|
||||
|
||||
relayData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Relay
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Relay Output Data (type: Relay, device: channel number)"
|
||||
properties:
|
||||
<init_fwd:
|
||||
type: boolean
|
||||
description: "If relay forward direction is initialized in the robot program"
|
||||
<init_rev:
|
||||
type: boolean
|
||||
description: "If relay reverse direction is initialized in the robot program"
|
||||
<fwd:
|
||||
type: boolean
|
||||
description: "True if forward direction is enabled"
|
||||
<rev:
|
||||
type: boolean
|
||||
description: "True if reverse direction is enabled"
|
||||
|
||||
solenoidData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: Solenoid
|
||||
device:
|
||||
type: string
|
||||
description: Device Identifier (usually channel)
|
||||
data:
|
||||
type: object
|
||||
description: "Solenoid Data (type: Solenoid, device: channel number)"
|
||||
properties:
|
||||
<init:
|
||||
type: boolean
|
||||
description: "If Solenoid is initialized in the robot program"
|
||||
<output:
|
||||
type: boolean
|
||||
description: "The state of the solenoid"
|
||||
|
||||
roborioData:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- device
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Device Type (e.g. DIO/AI/PWM/Encoder etc)
|
||||
const: RoboRIO
|
||||
device:
|
||||
type: string
|
||||
description: Should be left blank
|
||||
data:
|
||||
type: object
|
||||
description: "RoboRIO Data (type: RoboRIO)"
|
||||
properties:
|
||||
">fpga_button":
|
||||
type: boolean
|
||||
description: "FPGA button state"
|
||||
">vin_voltage":
|
||||
type: number
|
||||
description: "Vin rail voltage"
|
||||
">vin_current":
|
||||
type: number
|
||||
description: "Vin rail current"
|
||||
">6v_voltage":
|
||||
type: number
|
||||
description: "6V rail voltage"
|
||||
">6v_current":
|
||||
type: number
|
||||
description: "6V rail current"
|
||||
">6v_active":
|
||||
type: boolean
|
||||
description: "True if 6V rail active, false if inactive"
|
||||
">6v_faults":
|
||||
type: integer
|
||||
description: "Number of faults on 6V rail"
|
||||
">5v_voltage":
|
||||
type: number
|
||||
description: "5V rail voltage"
|
||||
">5v_current":
|
||||
type: number
|
||||
description: "5V rail current"
|
||||
">5v_active":
|
||||
type: boolean
|
||||
description: "True if 5V rail active, false if inactive"
|
||||
">5v_faults":
|
||||
type: integer
|
||||
description: "Number of faults on 5V rail"
|
||||
">3v3_voltage":
|
||||
type: number
|
||||
description: "3.3V rail voltage"
|
||||
">3v3_current":
|
||||
type: number
|
||||
description: "3.3V rail current"
|
||||
">3v3_active":
|
||||
type: boolean
|
||||
description: "True if 3.3V rail active, false if inactive"
|
||||
">3v3_faults":
|
||||
type: integer
|
||||
description: "Number of faults on 3.3V rail"
|
||||
@@ -71,6 +71,7 @@ void HALSimWSProviderSimDevice::OnNetValueChanged(const wpi::json& json) {
|
||||
break;
|
||||
case HAL_DOUBLE:
|
||||
value.data.v_double = it.value();
|
||||
value.data.v_double -= vd->second->doubleOffset;
|
||||
break;
|
||||
case HAL_ENUM: {
|
||||
if (it->is_string()) {
|
||||
@@ -97,9 +98,11 @@ void HALSimWSProviderSimDevice::OnNetValueChanged(const wpi::json& json) {
|
||||
}
|
||||
case HAL_INT:
|
||||
value.data.v_int = it.value();
|
||||
value.data.v_int -= vd->second->intOffset;
|
||||
break;
|
||||
case HAL_LONG:
|
||||
value.data.v_long = it.value();
|
||||
value.data.v_long -= vd->second->intOffset;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -186,7 +189,8 @@ void HALSimWSProviderSimDevice::OnValueChanged(SimDeviceValueData* valueData,
|
||||
ProcessHalCallback({{valueData->key, value->data.v_boolean}});
|
||||
break;
|
||||
case HAL_DOUBLE:
|
||||
ProcessHalCallback({{valueData->key, value->data.v_double}});
|
||||
ProcessHalCallback(
|
||||
{{valueData->key, value->data.v_double + valueData->doubleOffset}});
|
||||
break;
|
||||
case HAL_ENUM: {
|
||||
int v = value->data.v_enum;
|
||||
@@ -198,10 +202,12 @@ void HALSimWSProviderSimDevice::OnValueChanged(SimDeviceValueData* valueData,
|
||||
break;
|
||||
}
|
||||
case HAL_INT:
|
||||
ProcessHalCallback({{valueData->key, value->data.v_int}});
|
||||
ProcessHalCallback(
|
||||
{{valueData->key, value->data.v_int + valueData->intOffset}});
|
||||
break;
|
||||
case HAL_LONG:
|
||||
ProcessHalCallback({{valueData->key, value->data.v_long}});
|
||||
ProcessHalCallback(
|
||||
{{valueData->key, value->data.v_long + valueData->intOffset}});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -209,6 +215,33 @@ void HALSimWSProviderSimDevice::OnValueChanged(SimDeviceValueData* valueData,
|
||||
}
|
||||
}
|
||||
|
||||
void HALSimWSProviderSimDevice::OnValueResetStatic(
|
||||
const char* name, void* param, HAL_SimValueHandle handle, int32_t direction,
|
||||
const struct HAL_Value* value) {
|
||||
auto valueData = (reinterpret_cast<SimDeviceValueData*>(param));
|
||||
valueData->device->OnValueReset(valueData, value);
|
||||
}
|
||||
|
||||
void HALSimWSProviderSimDevice::OnValueReset(SimDeviceValueData* valueData,
|
||||
const struct HAL_Value* value) {
|
||||
switch (value->type) {
|
||||
case HAL_BOOLEAN:
|
||||
case HAL_ENUM:
|
||||
break;
|
||||
case HAL_DOUBLE:
|
||||
valueData->doubleOffset += value->data.v_double;
|
||||
break;
|
||||
case HAL_INT:
|
||||
valueData->intOffset += value->data.v_int;
|
||||
break;
|
||||
case HAL_LONG:
|
||||
valueData->intOffset += value->data.v_long;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HALSimWSProviderSimDevice::ProcessHalCallback(const wpi::json& payload) {
|
||||
auto ws = m_ws.lock();
|
||||
if (ws) {
|
||||
|
||||
@@ -28,6 +28,8 @@ struct SimDeviceValueData {
|
||||
std::vector<std::string> options;
|
||||
std::vector<double> optionValues;
|
||||
HAL_Type valueType;
|
||||
double doubleOffset = 0;
|
||||
int64_t intOffset = 0;
|
||||
};
|
||||
|
||||
class HALSimWSProviderSimDevice : public HALSimWSBaseProvider {
|
||||
@@ -63,6 +65,12 @@ class HALSimWSProviderSimDevice : public HALSimWSBaseProvider {
|
||||
void OnValueChanged(SimDeviceValueData* valueData,
|
||||
const struct HAL_Value* value);
|
||||
|
||||
static void OnValueResetStatic(const char* name, void* param,
|
||||
HAL_SimValueHandle handle, int32_t direction,
|
||||
const struct HAL_Value* value);
|
||||
void OnValueReset(SimDeviceValueData* valueData,
|
||||
const struct HAL_Value* value);
|
||||
|
||||
void CancelCallbacks();
|
||||
|
||||
wpi::StringMap<std::unique_ptr<SimDeviceValueData>> m_valueHandles;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||
<suppressions>
|
||||
<suppress files=".*sim.*"
|
||||
checks="(LineLength|EmptyLineSeparator|ParameterName|ImportOrder|AbbreviationAsWordInName|JavadocMethod|NoFinalizer)" />
|
||||
<suppress files=".*test.*" checks="MissingJavadocMethod" />
|
||||
<suppress files=".*wpilibjIntegrationTests.*"
|
||||
checks="MissingJavadocMethod" />
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// 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.
|
||||
|
||||
//
|
||||
// Portable File Dialogs
|
||||
|
||||
@@ -193,9 +193,11 @@ bool gui::Initialize(const char* title, int width, int height) {
|
||||
}
|
||||
|
||||
if (!gContext->loadedWidthHeight) {
|
||||
#ifndef __APPLE__
|
||||
if (windowScale == 1.0) {
|
||||
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
||||
}
|
||||
#endif
|
||||
// force user scale if window scale is smaller
|
||||
if (windowScale <= 0.5) {
|
||||
gContext->userScale = 0;
|
||||
@@ -211,7 +213,23 @@ bool gui::Initialize(const char* title, int width, int height) {
|
||||
|
||||
// Update window settings
|
||||
if (gContext->xPos != -1 && gContext->yPos != -1) {
|
||||
glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
|
||||
// check to make sure the position isn't off-screen
|
||||
bool found = false;
|
||||
int monCount;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&monCount);
|
||||
for (int i = 0; i < monCount; ++i) {
|
||||
int monXPos, monYPos, monWidth, monHeight;
|
||||
glfwGetMonitorWorkarea(monitors[i], &monXPos, &monYPos, &monWidth,
|
||||
&monHeight);
|
||||
if (gContext->xPos >= monXPos && gContext->xPos < (monXPos + monWidth) &&
|
||||
gContext->yPos >= monYPos && gContext->yPos < (monYPos + monHeight)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
glfwSetWindowPos(gContext->window, gContext->xPos, gContext->yPos);
|
||||
}
|
||||
glfwShowWindow(gContext->window);
|
||||
}
|
||||
|
||||
@@ -284,7 +302,9 @@ void gui::CommonRenderFrame() {
|
||||
|
||||
// Scale based on OS window content scaling
|
||||
float windowScale = 1.0;
|
||||
#ifndef __APPLE__
|
||||
glfwGetWindowContentScale(gContext->window, &windowScale, nullptr);
|
||||
#endif
|
||||
// map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
|
||||
// 4 = 1.5x, 5 = 1.75x, 6 = 2x
|
||||
gContext->fontScale = std::clamp(
|
||||
@@ -394,12 +414,14 @@ void gui::ConfigurePlatformSaveFile(const std::string& name) {
|
||||
gContext->iniPath = name;
|
||||
#if defined(_MSC_VER)
|
||||
const char* env = std::getenv("APPDATA");
|
||||
if (env)
|
||||
if (env) {
|
||||
gContext->iniPath = env + std::string("/" + name);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
const char* env = std::getenv("HOME");
|
||||
if (env)
|
||||
if (env) {
|
||||
gContext->iniPath = env + std::string("/Library/Preferences/" + name);
|
||||
}
|
||||
#else
|
||||
const char* xdg = std::getenv("XDG_CONFIG_HOME");
|
||||
const char* env = std::getenv("HOME");
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
// 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.
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
|
||||
@@ -56,7 +56,7 @@ public abstract class CommandBase implements Sendable, Command {
|
||||
* @param name name
|
||||
* @return the decorated Command
|
||||
*/
|
||||
public Command withName(String name) {
|
||||
public CommandBase withName(String name) {
|
||||
this.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.WeakHashMap;
|
||||
* those commands are not also used independently, which can result in inconsistent command state
|
||||
* and unpredictable execution.
|
||||
*/
|
||||
public abstract class CommandGroupBase extends CommandBase implements Command {
|
||||
public abstract class CommandGroupBase extends CommandBase {
|
||||
private static final Set<Command> m_groupedCommands =
|
||||
Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
|
||||
@@ -450,7 +450,8 @@ public final class CommandScheduler implements Sendable, AutoCloseable {
|
||||
* requiring the subsystem
|
||||
*
|
||||
* @param subsystem the subsystem to be inquired about
|
||||
* @return the command currently requiring the subsystem
|
||||
* @return the command currently requiring the subsystem, or null if no command is currently
|
||||
* scheduled
|
||||
*/
|
||||
public Command requiring(Subsystem subsystem) {
|
||||
return m_requirements.get(subsystem);
|
||||
|
||||
@@ -200,6 +200,10 @@ public class RamseteCommand extends CommandBase {
|
||||
@Override
|
||||
public void end(boolean interrupted) {
|
||||
m_timer.stop();
|
||||
|
||||
if (interrupted) {
|
||||
m_output.accept(0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -146,6 +146,14 @@ void RamseteCommand::Execute() {
|
||||
|
||||
void RamseteCommand::End(bool interrupted) {
|
||||
m_timer.Stop();
|
||||
|
||||
if (interrupted) {
|
||||
if (m_usePID) {
|
||||
m_outputVolts(0_V, 0_V);
|
||||
} else {
|
||||
m_outputVel(0_mps, 0_mps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RamseteCommand::IsFinished() {
|
||||
|
||||
@@ -31,7 +31,7 @@ class SwerveControllerCommandTest : public ::testing::Test {
|
||||
frc2::Timer m_timer;
|
||||
frc::Rotation2d m_angle{0_rad};
|
||||
|
||||
std::array<frc::SwerveModuleState, 4> m_moduleStates{
|
||||
wpi::array<frc::SwerveModuleState, 4> m_moduleStates{
|
||||
frc::SwerveModuleState{}, frc::SwerveModuleState{},
|
||||
frc::SwerveModuleState{}, frc::SwerveModuleState{}};
|
||||
|
||||
@@ -60,7 +60,7 @@ class SwerveControllerCommandTest : public ::testing::Test {
|
||||
|
||||
void TearDown() override { frc::sim::ResumeTiming(); }
|
||||
|
||||
std::array<frc::SwerveModuleState, 4> getCurrentWheelSpeeds() {
|
||||
wpi::array<frc::SwerveModuleState, 4> getCurrentWheelSpeeds() {
|
||||
return m_moduleStates;
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ public abstract class Command implements Sendable, AutoCloseable {
|
||||
* @return the requirements (as an {@link Enumeration Enumeration} of {@link Subsystem
|
||||
* Subsystems}) of this command
|
||||
*/
|
||||
synchronized Enumeration getRequirements() {
|
||||
synchronized Enumeration<?> getRequirements() {
|
||||
return m_requirements.getElements();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user