mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Compare commits
87 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 |
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
|
||||
|
||||
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -71,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
|
||||
@@ -87,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 }}
|
||||
@@ -136,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 @@
|
||||
# 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
|
||||
|
||||
|
||||
@@ -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,7 +106,6 @@ 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.")
|
||||
destinationDirectory = outputsFolder
|
||||
@@ -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(
|
||||
|
||||
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,6 +113,15 @@ 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);
|
||||
|
||||
@@ -123,6 +132,7 @@ class Plot {
|
||||
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;
|
||||
@@ -164,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() {
|
||||
@@ -181,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()};
|
||||
@@ -364,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();
|
||||
}
|
||||
@@ -527,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)) {
|
||||
@@ -582,12 +605,12 @@ 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_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"
|
||||
@@ -761,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")) {
|
||||
@@ -844,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;
|
||||
@@ -925,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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -140,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.
|
||||
*
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -708,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.79-1"
|
||||
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) {
|
||||
|
||||
@@ -779,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();
|
||||
|
||||
@@ -792,7 +796,7 @@ 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 > config.maxAbsValue) {
|
||||
axisValue = config.maxAbsValue;
|
||||
@@ -805,7 +809,7 @@ void KeyboardJoystick::Update() {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.decKey >= 0 && io.KeysDown[config.decKey]) {
|
||||
if (IsKeyDown(io, config.decKey)) {
|
||||
axisValue -= config.keyRate;
|
||||
if (axisValue < -config.maxAbsValue) {
|
||||
axisValue = -config.maxAbsValue;
|
||||
@@ -823,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;
|
||||
}
|
||||
@@ -834,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;
|
||||
}
|
||||
}
|
||||
@@ -970,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);
|
||||
@@ -993,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") {
|
||||
@@ -1348,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");
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public class CommandGroup extends Command {
|
||||
command.setParent(this);
|
||||
|
||||
m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE));
|
||||
for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public class CommandGroup extends Command {
|
||||
command.setParent(this);
|
||||
|
||||
m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE, timeout));
|
||||
for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class CommandGroup extends Command {
|
||||
command.setParent(this);
|
||||
|
||||
m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD));
|
||||
for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ public class CommandGroup extends Command {
|
||||
command.setParent(this);
|
||||
|
||||
m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD, timeout));
|
||||
for (Enumeration e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = command.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@ public class CommandGroup extends Command {
|
||||
cmd.removed();
|
||||
}
|
||||
|
||||
Enumeration children = m_children.elements();
|
||||
Enumeration<?> children = m_children.elements();
|
||||
while (children.hasMoreElements()) {
|
||||
Command cmd = ((Entry) children.nextElement()).m_command;
|
||||
cmd._cancel();
|
||||
@@ -361,7 +361,7 @@ public class CommandGroup extends Command {
|
||||
for (int i = 0; i < m_children.size(); i++) {
|
||||
Command child = m_children.elementAt(i).m_command;
|
||||
|
||||
Enumeration requirements = command.getRequirements();
|
||||
Enumeration<?> requirements = command.getRequirements();
|
||||
|
||||
while (requirements.hasMoreElements()) {
|
||||
Object requirement = requirements.nextElement();
|
||||
|
||||
@@ -37,13 +37,13 @@ public abstract class ConditionalCommand extends Command {
|
||||
|
||||
private void requireAll() {
|
||||
if (m_onTrue != null) {
|
||||
for (Enumeration e = m_onTrue.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = m_onTrue.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_onFalse != null) {
|
||||
for (Enumeration e = m_onFalse.getRequirements(); e.hasMoreElements(); ) {
|
||||
for (Enumeration<?> e = m_onFalse.getRequirements(); e.hasMoreElements(); ) {
|
||||
requires((Subsystem) e.nextElement());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public final class Scheduler implements Sendable, AutoCloseable {
|
||||
// Only add if not already in
|
||||
if (!m_commandTable.containsKey(command)) {
|
||||
// Check that the requirements can be had
|
||||
Enumeration requirements = command.getRequirements();
|
||||
Enumeration<?> requirements = command.getRequirements();
|
||||
while (requirements.hasMoreElements()) {
|
||||
Subsystem lock = (Subsystem) requirements.nextElement();
|
||||
if (lock.getCurrentCommand() != null && !lock.getCurrentCommand().isInterruptible()) {
|
||||
@@ -210,7 +210,7 @@ public final class Scheduler implements Sendable, AutoCloseable {
|
||||
}
|
||||
|
||||
// Call every subsystem's periodic method
|
||||
Enumeration subsystems = m_subsystems.getElements();
|
||||
Enumeration<?> subsystems = m_subsystems.getElements();
|
||||
while (subsystems.hasMoreElements()) {
|
||||
((Subsystem) subsystems.nextElement()).periodic();
|
||||
}
|
||||
@@ -233,7 +233,7 @@ public final class Scheduler implements Sendable, AutoCloseable {
|
||||
m_additions.removeAllElements();
|
||||
|
||||
// Add in the defaults
|
||||
Enumeration locks = m_subsystems.getElements();
|
||||
Enumeration<?> locks = m_subsystems.getElements();
|
||||
while (locks.hasMoreElements()) {
|
||||
Subsystem lock = (Subsystem) locks.nextElement();
|
||||
if (lock.getCurrentCommand() == null) {
|
||||
@@ -276,7 +276,7 @@ public final class Scheduler implements Sendable, AutoCloseable {
|
||||
}
|
||||
element.remove();
|
||||
|
||||
Enumeration requirements = command.getRequirements();
|
||||
Enumeration<?> requirements = command.getRequirements();
|
||||
while (requirements.hasMoreElements()) {
|
||||
((Subsystem) requirements.nextElement()).setCurrentCommand(null);
|
||||
}
|
||||
|
||||
@@ -30,10 +30,9 @@ public final class MockHardwareExtension implements BeforeAllCallback {
|
||||
|
||||
private void initializeHardware() {
|
||||
HAL.initialize(500, 0);
|
||||
DriverStationSim dsSim = new DriverStationSim();
|
||||
dsSim.setDsAttached(true);
|
||||
dsSim.setAutonomous(false);
|
||||
dsSim.setEnabled(true);
|
||||
dsSim.setTest(true);
|
||||
DriverStationSim.setDsAttached(true);
|
||||
DriverStationSim.setAutonomous(false);
|
||||
DriverStationSim.setEnabled(true);
|
||||
DriverStationSim.setTest(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,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 '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
|
||||
using namespace frc;
|
||||
|
||||
AnalogEncoder::AnalogEncoder(int channel)
|
||||
: AnalogEncoder(std::make_shared<AnalogInput>(channel)) {}
|
||||
|
||||
AnalogEncoder::AnalogEncoder(AnalogInput& analogInput)
|
||||
: m_analogInput{&analogInput, NullDeleter<AnalogInput>{}},
|
||||
m_analogTrigger{m_analogInput.get()},
|
||||
|
||||
@@ -236,14 +236,10 @@ int Compressor::GetModule() const {
|
||||
void Compressor::InitSendable(SendableBuilder& builder) {
|
||||
builder.SetSmartDashboardType("Compressor");
|
||||
builder.AddBooleanProperty(
|
||||
"Enabled", [=]() { return Enabled(); },
|
||||
[=](bool value) {
|
||||
if (value) {
|
||||
Start();
|
||||
} else {
|
||||
Stop();
|
||||
}
|
||||
});
|
||||
"Closed Loop Control", [=]() { return GetClosedLoopControl(); },
|
||||
[=](bool value) { SetClosedLoopControl(value); });
|
||||
builder.AddBooleanProperty(
|
||||
"Enabled", [=] { return Enabled(); }, nullptr);
|
||||
builder.AddBooleanProperty(
|
||||
"Pressure switch", [=]() { return GetPressureSwitchValue(); }, nullptr);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,14 @@ void DoubleSolenoid::Toggle() {
|
||||
}
|
||||
}
|
||||
|
||||
int DoubleSolenoid::GetFwdChannel() const {
|
||||
return m_forwardChannel;
|
||||
}
|
||||
|
||||
int DoubleSolenoid::GetRevChannel() const {
|
||||
return m_reverseChannel;
|
||||
}
|
||||
|
||||
bool DoubleSolenoid::IsFwdSolenoidBlackListed() const {
|
||||
int blackList = GetPCMSolenoidBlackList(m_moduleNumber);
|
||||
return (blackList & m_forwardMask) != 0;
|
||||
|
||||
@@ -200,3 +200,8 @@ void Notifier::UpdateAlarm(uint64_t triggerTime) {
|
||||
void Notifier::UpdateAlarm() {
|
||||
UpdateAlarm(static_cast<uint64_t>(m_expirationTime * 1e6));
|
||||
}
|
||||
|
||||
bool Notifier::SetHALThreadPriority(bool realTime, int32_t priority) {
|
||||
int32_t status = 0;
|
||||
return HAL_SetNotifierThreadPriority(realTime, priority, &status);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ bool RobotController::GetUserButton() {
|
||||
return value;
|
||||
}
|
||||
|
||||
units::volt_t RobotController::GetBatteryVoltage() {
|
||||
int32_t status = 0;
|
||||
double retVal = HAL_GetVinVoltage(&status);
|
||||
wpi_setGlobalHALError(status);
|
||||
return units::volt_t{retVal};
|
||||
}
|
||||
|
||||
bool RobotController::IsSysActive() {
|
||||
int32_t status = 0;
|
||||
bool retVal = HAL_GetSystemActive(&status);
|
||||
|
||||
@@ -79,6 +79,10 @@ void Solenoid::Toggle() {
|
||||
Set(!Get());
|
||||
}
|
||||
|
||||
int Solenoid::GetChannel() const {
|
||||
return m_channel;
|
||||
}
|
||||
|
||||
bool Solenoid::IsBlackListed() const {
|
||||
int value = GetPCMSolenoidBlackList(m_moduleNumber) & (1 << m_channel);
|
||||
return (value != 0);
|
||||
|
||||
@@ -58,3 +58,7 @@ void SolenoidBase::ClearAllPCMStickyFaults() {
|
||||
}
|
||||
|
||||
SolenoidBase::SolenoidBase(int moduleNumber) : m_moduleNumber(moduleNumber) {}
|
||||
|
||||
int SolenoidBase::GetModuleNumber() const {
|
||||
return m_moduleNumber;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ HolonomicDriveController::HolonomicDriveController(
|
||||
|
||||
bool HolonomicDriveController::AtReference() const {
|
||||
const auto& eTranslate = m_poseError.Translation();
|
||||
const auto& eRotate = m_poseError.Rotation();
|
||||
const auto& eRotate = m_rotationError;
|
||||
const auto& tolTranslate = m_poseTolerance.Translation();
|
||||
const auto& tolRotate = m_poseTolerance.Rotation();
|
||||
return units::math::abs(eTranslate.X()) < tolTranslate.X() &&
|
||||
@@ -34,6 +34,13 @@ void HolonomicDriveController::SetTolerance(const Pose2d& tolerance) {
|
||||
ChassisSpeeds HolonomicDriveController::Calculate(
|
||||
const Pose2d& currentPose, const Pose2d& poseRef,
|
||||
units::meters_per_second_t linearVelocityRef, const Rotation2d& angleRef) {
|
||||
// If this is the first run, then we need to reset the theta controller to the
|
||||
// current pose's heading.
|
||||
if (m_firstRun) {
|
||||
m_thetaController.Reset(currentPose.Rotation().Radians());
|
||||
m_firstRun = false;
|
||||
}
|
||||
|
||||
// Calculate feedforward velocities (field-relative)
|
||||
auto xFF = linearVelocityRef * poseRef.Rotation().Cos();
|
||||
auto yFF = linearVelocityRef * poseRef.Rotation().Sin();
|
||||
@@ -41,6 +48,7 @@ ChassisSpeeds HolonomicDriveController::Calculate(
|
||||
currentPose.Rotation().Radians(), angleRef.Radians()));
|
||||
|
||||
m_poseError = poseRef.RelativeTo(currentPose);
|
||||
m_rotationError = angleRef - currentPose.Rotation();
|
||||
|
||||
if (!m_enabled) {
|
||||
return ChassisSpeeds::FromFieldRelativeSpeeds(xFF, yFF, thetaFF,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
|
||||
#include "frc/DriverStation.h"
|
||||
#include "frc/MathUtil.h"
|
||||
#include "frc/smartdashboard/SendableBuilder.h"
|
||||
#include "frc/smartdashboard/SendableRegistry.h"
|
||||
@@ -18,6 +19,12 @@ using namespace frc2;
|
||||
PIDController::PIDController(double Kp, double Ki, double Kd,
|
||||
units::second_t period)
|
||||
: m_Kp(Kp), m_Ki(Ki), m_Kd(Kd), m_period(period) {
|
||||
if (period <= 0_s) {
|
||||
frc::DriverStation::ReportError(
|
||||
"Controller period must be a non-zero positive number!");
|
||||
m_period = 20_ms;
|
||||
frc::DriverStation::ReportWarning("Controller period defaulted to 20ms.");
|
||||
}
|
||||
static int instances = 0;
|
||||
instances++;
|
||||
HAL_Report(HALUsageReporting::kResourceType_PIDController2, instances);
|
||||
|
||||
91
wpilibc/src/main/native/cpp/simulation/DoubleSolenoidSim.cpp
Normal file
91
wpilibc/src/main/native/cpp/simulation/DoubleSolenoidSim.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 "frc/simulation/DoubleSolenoidSim.h"
|
||||
|
||||
#include "frc/SensorUtil.h"
|
||||
#include "frc/simulation/PCMSim.h"
|
||||
|
||||
using namespace frc;
|
||||
using namespace frc::sim;
|
||||
|
||||
DoubleSolenoidSim::DoubleSolenoidSim(int fwd, int rev)
|
||||
: m_fwd{fwd}, m_rev{rev} {}
|
||||
|
||||
DoubleSolenoidSim::DoubleSolenoidSim(int module, int fwd, int rev)
|
||||
: m_pcm{module}, m_fwd{fwd}, m_rev{rev} {}
|
||||
|
||||
DoubleSolenoidSim::DoubleSolenoidSim(PCMSim& pcm, int fwd, int rev)
|
||||
: m_pcm{pcm}, m_fwd{fwd}, m_rev{rev} {}
|
||||
|
||||
DoubleSolenoidSim::DoubleSolenoidSim(DoubleSolenoid& solenoid)
|
||||
: m_pcm{solenoid.GetModuleNumber()},
|
||||
m_fwd{solenoid.GetFwdChannel()},
|
||||
m_rev{solenoid.GetRevChannel()} {}
|
||||
|
||||
std::unique_ptr<CallbackStore>
|
||||
DoubleSolenoidSim::RegisterFwdInitializedCallback(NotifyCallback callback,
|
||||
bool initialNotify) {
|
||||
return m_pcm.RegisterSolenoidInitializedCallback(m_fwd, callback,
|
||||
initialNotify);
|
||||
}
|
||||
|
||||
bool DoubleSolenoidSim::GetFwdInitialized() const {
|
||||
return m_pcm.GetSolenoidInitialized(m_fwd);
|
||||
}
|
||||
|
||||
void DoubleSolenoidSim::SetFwdInitialized(bool initialized) {
|
||||
m_pcm.SetSolenoidInitialized(m_fwd, initialized);
|
||||
}
|
||||
|
||||
std::unique_ptr<CallbackStore>
|
||||
DoubleSolenoidSim::RegisterRevInitializedCallback(NotifyCallback callback,
|
||||
bool initialNotify) {
|
||||
return m_pcm.RegisterSolenoidInitializedCallback(m_rev, callback,
|
||||
initialNotify);
|
||||
}
|
||||
|
||||
bool DoubleSolenoidSim::GetRevInitialized() const {
|
||||
return m_pcm.GetSolenoidInitialized(m_rev);
|
||||
}
|
||||
|
||||
void DoubleSolenoidSim::SetRevInitialized(bool initialized) {
|
||||
m_pcm.SetSolenoidInitialized(m_rev, initialized);
|
||||
}
|
||||
|
||||
void DoubleSolenoidSim::Set(DoubleSolenoid::Value value) {
|
||||
bool forward = false;
|
||||
bool reverse = false;
|
||||
|
||||
switch (value) {
|
||||
case DoubleSolenoid::Value::kOff:
|
||||
forward = false;
|
||||
reverse = false;
|
||||
break;
|
||||
case DoubleSolenoid::Value::kForward:
|
||||
forward = true;
|
||||
reverse = false;
|
||||
break;
|
||||
case DoubleSolenoid::Value::kReverse:
|
||||
forward = false;
|
||||
reverse = true;
|
||||
break;
|
||||
}
|
||||
|
||||
m_pcm.SetSolenoidOutput(m_fwd, forward);
|
||||
m_pcm.SetSolenoidOutput(m_rev, reverse);
|
||||
}
|
||||
|
||||
DoubleSolenoid::Value DoubleSolenoidSim::Get() const {
|
||||
bool valueForward = m_pcm.GetSolenoidOutput(m_fwd);
|
||||
bool valueReverse = m_pcm.GetSolenoidOutput(m_rev);
|
||||
|
||||
if (valueForward) {
|
||||
return DoubleSolenoid::Value::kForward;
|
||||
} else if (valueReverse) {
|
||||
return DoubleSolenoid::Value::kReverse;
|
||||
} else {
|
||||
return DoubleSolenoid::Value::kOff;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <hal/SimDevice.h>
|
||||
#include <hal/simulation/SimDeviceData.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
|
||||
using namespace frc;
|
||||
using namespace frc::sim;
|
||||
@@ -16,6 +18,22 @@ using namespace frc::sim;
|
||||
SimDeviceSim::SimDeviceSim(const char* name)
|
||||
: m_handle{HALSIM_GetSimDeviceHandle(name)} {}
|
||||
|
||||
SimDeviceSim::SimDeviceSim(const char* name, int index) {
|
||||
wpi::SmallString<128> fullname;
|
||||
wpi::raw_svector_ostream os(fullname);
|
||||
os << name << '[' << index << ']';
|
||||
|
||||
m_handle = HALSIM_GetSimDeviceHandle(fullname.c_str());
|
||||
}
|
||||
|
||||
SimDeviceSim::SimDeviceSim(const char* name, int index, int channel) {
|
||||
wpi::SmallString<128> fullname;
|
||||
wpi::raw_svector_ostream os(fullname);
|
||||
os << name << '[' << index << ',' << channel << ']';
|
||||
|
||||
m_handle = HALSIM_GetSimDeviceHandle(fullname.c_str());
|
||||
}
|
||||
|
||||
hal::SimValue SimDeviceSim::GetValue(const char* name) const {
|
||||
return HALSIM_GetSimValueHandle(m_handle, name);
|
||||
}
|
||||
|
||||
50
wpilibc/src/main/native/cpp/simulation/SolenoidSim.cpp
Normal file
50
wpilibc/src/main/native/cpp/simulation/SolenoidSim.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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 "frc/simulation/SolenoidSim.h"
|
||||
|
||||
#include "frc/SensorUtil.h"
|
||||
#include "frc/simulation/PCMSim.h"
|
||||
|
||||
using namespace frc;
|
||||
using namespace frc::sim;
|
||||
|
||||
SolenoidSim::SolenoidSim(int channel) : m_channel{channel} {}
|
||||
|
||||
SolenoidSim::SolenoidSim(int module, int channel)
|
||||
: m_pcm{module}, m_channel{channel} {}
|
||||
|
||||
SolenoidSim::SolenoidSim(PCMSim& pcm, int channel)
|
||||
: m_pcm{pcm}, m_channel{channel} {}
|
||||
|
||||
SolenoidSim::SolenoidSim(Solenoid& solenoid)
|
||||
: m_pcm{solenoid.GetModuleNumber()}, m_channel{solenoid.GetChannel()} {}
|
||||
|
||||
std::unique_ptr<CallbackStore> SolenoidSim::RegisterInitializedCallback(
|
||||
NotifyCallback callback, bool initialNotify) {
|
||||
return m_pcm.RegisterSolenoidInitializedCallback(m_channel, callback,
|
||||
initialNotify);
|
||||
}
|
||||
|
||||
bool SolenoidSim::GetInitialized() const {
|
||||
return m_pcm.GetSolenoidInitialized(m_channel);
|
||||
}
|
||||
|
||||
void SolenoidSim::SetInitialized(bool initialized) {
|
||||
m_pcm.SetSolenoidInitialized(m_channel, initialized);
|
||||
}
|
||||
|
||||
std::unique_ptr<CallbackStore> SolenoidSim::RegisterOutputCallback(
|
||||
NotifyCallback callback, bool initialNotify) {
|
||||
return m_pcm.RegisterSolenoidOutputCallback(m_channel, callback,
|
||||
initialNotify);
|
||||
}
|
||||
|
||||
bool SolenoidSim::GetOutput() const {
|
||||
return m_pcm.GetSolenoidOutput(m_channel);
|
||||
}
|
||||
|
||||
void SolenoidSim::SetOutput(bool output) {
|
||||
m_pcm.SetSolenoidOutput(m_channel, output);
|
||||
}
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
#include "frc/smartdashboard/FieldObject2d.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/Endian.h>
|
||||
#include <wpi/MathExtras.h>
|
||||
|
||||
#include "frc/trajectory/Trajectory.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
FieldObject2d::FieldObject2d(FieldObject2d&& rhs) {
|
||||
@@ -48,6 +55,16 @@ void FieldObject2d::SetPoses(std::initializer_list<Pose2d> poses) {
|
||||
SetPoses(wpi::makeArrayRef(poses.begin(), poses.end()));
|
||||
}
|
||||
|
||||
void FieldObject2d::SetTrajectory(const Trajectory& trajectory) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_poses.clear();
|
||||
m_poses.reserve(trajectory.States().size());
|
||||
for (auto&& state : trajectory.States()) {
|
||||
m_poses.push_back(state.pose);
|
||||
}
|
||||
UpdateEntry();
|
||||
}
|
||||
|
||||
std::vector<Pose2d> FieldObject2d::GetPoses() const {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
UpdateFromEntry();
|
||||
@@ -66,17 +83,41 @@ void FieldObject2d::UpdateEntry(bool setDefault) {
|
||||
if (!m_entry) {
|
||||
return;
|
||||
}
|
||||
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>());
|
||||
}
|
||||
if (setDefault) {
|
||||
m_entry.SetDefaultDoubleArray(arr);
|
||||
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>());
|
||||
}
|
||||
if (setDefault) {
|
||||
m_entry.SetDefaultDoubleArray(arr);
|
||||
} else {
|
||||
m_entry.ForceSetDoubleArray(arr);
|
||||
}
|
||||
} else {
|
||||
m_entry.SetDoubleArray(arr);
|
||||
// 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;
|
||||
}
|
||||
if (setDefault) {
|
||||
m_entry.SetDefaultRaw(wpi::StringRef{arr.data(), arr.size()});
|
||||
} else {
|
||||
m_entry.ForceSetRaw(wpi::StringRef{arr.data(), arr.size()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +126,45 @@ void FieldObject2d::UpdateFromEntry() const {
|
||||
return;
|
||||
}
|
||||
auto val = m_entry.GetValue();
|
||||
if (!val || !val->IsDoubleArray()) {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
auto arr = val->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] =
|
||||
Pose2d{units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
|
||||
Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
|
||||
|
||||
if (val->IsDoubleArray()) {
|
||||
auto arr = val->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] =
|
||||
Pose2d{units::meter_t{arr[i * 3 + 0]}, units::meter_t{arr[i * 3 + 1]},
|
||||
Rotation2d{units::degree_t{arr[i * 3 + 2]}}};
|
||||
}
|
||||
} else if (val->IsRaw()) {
|
||||
// treat it simply as an array of doubles
|
||||
wpi::StringRef data = val->GetRaw();
|
||||
|
||||
// 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] = Pose2d{units::meter_t{x}, units::meter_t{y},
|
||||
Rotation2d{units::degree_t{rot}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,13 @@ class AnalogEncoder : public ErrorBase,
|
||||
public Sendable,
|
||||
public SendableHelper<AnalogEncoder> {
|
||||
public:
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogIn channel.
|
||||
*
|
||||
* @param channel the analog input channel to attach to
|
||||
*/
|
||||
explicit AnalogEncoder(int channel);
|
||||
|
||||
/**
|
||||
* Construct a new AnalogEncoder attached to a specific AnalogInput.
|
||||
*
|
||||
|
||||
@@ -74,6 +74,20 @@ class DoubleSolenoid : public SolenoidBase,
|
||||
*/
|
||||
void Toggle();
|
||||
|
||||
/**
|
||||
* Get the forward channel.
|
||||
*
|
||||
* @return the forward channel.
|
||||
*/
|
||||
int GetFwdChannel() const;
|
||||
|
||||
/**
|
||||
* Get the reverse channel.
|
||||
*
|
||||
* @return the reverse channel.
|
||||
*/
|
||||
int GetRevChannel() const;
|
||||
|
||||
/**
|
||||
* Check if the forward solenoid is blacklisted.
|
||||
*
|
||||
|
||||
@@ -46,8 +46,9 @@ class Notifier : public ErrorBase {
|
||||
* This is useful for reducing scheduling jitter on processes which are
|
||||
* sensitive to timing variance, like model-based control.
|
||||
*
|
||||
* @param priority The FIFO real-time scheduler priority ([0..100] where a
|
||||
* lower number represents higher priority).
|
||||
* @param priority The FIFO real-time scheduler priority ([1..99] where a
|
||||
* higher number represents higher priority). See "man 7
|
||||
* sched" for more details.
|
||||
* @param handler The handler is called at the notification time which is set
|
||||
* using StartSingle or StartPeriodic.
|
||||
*/
|
||||
@@ -142,6 +143,23 @@ class Notifier : public ErrorBase {
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return True on success.
|
||||
*/
|
||||
static bool SetHALThreadPriority(bool realTime, int32_t priority);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Update the HAL alarm time.
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <units/voltage.h>
|
||||
|
||||
namespace frc {
|
||||
|
||||
struct CANStatus {
|
||||
@@ -55,6 +57,13 @@ class RobotController {
|
||||
*/
|
||||
static bool GetUserButton();
|
||||
|
||||
/**
|
||||
* Read the battery voltage.
|
||||
*
|
||||
* @return The battery voltage in Volts.
|
||||
*/
|
||||
static units::volt_t GetBatteryVoltage();
|
||||
|
||||
/**
|
||||
* Check if the FPGA outputs are enabled.
|
||||
*
|
||||
|
||||
@@ -66,6 +66,11 @@ class Solenoid : public SolenoidBase,
|
||||
*/
|
||||
void Toggle();
|
||||
|
||||
/**
|
||||
* Get the channel this solenoid is connected to.
|
||||
*/
|
||||
int GetChannel() const;
|
||||
|
||||
/**
|
||||
* Check if solenoid is blacklisted.
|
||||
*
|
||||
|
||||
@@ -14,6 +14,13 @@ namespace frc {
|
||||
*/
|
||||
class SolenoidBase : public ErrorBase {
|
||||
public:
|
||||
/**
|
||||
* Get the CAN ID of the module this solenoid is connected to.
|
||||
*
|
||||
* @return the module number.
|
||||
*/
|
||||
int GetModuleNumber() const;
|
||||
|
||||
/**
|
||||
* Read all 8 solenoids as a single byte
|
||||
*
|
||||
|
||||
@@ -12,16 +12,20 @@ namespace frc {
|
||||
* Get the thread priority for the specified thread.
|
||||
*
|
||||
* @param thread Reference to the thread to get the priority for.
|
||||
* @param isRealTime Set to true if thread is realtime, otherwise false.
|
||||
* @return The current thread priority. Scaled 1-99, with 1 being highest.
|
||||
* @param isRealTime Set to true if thread is real-time, otherwise false.
|
||||
* @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.
|
||||
*/
|
||||
int GetThreadPriority(std::thread& thread, bool* isRealTime);
|
||||
|
||||
/**
|
||||
* Get the thread priority for the current thread
|
||||
*
|
||||
* @param isRealTime Set to true if thread is realtime, otherwise false.
|
||||
* @return The current thread priority. Scaled 1-99.
|
||||
* @param isRealTime Set to true if thread is real-time, otherwise false.
|
||||
* @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.
|
||||
*/
|
||||
int GetCurrentThreadPriority(bool* isRealTime);
|
||||
|
||||
@@ -29,26 +33,24 @@ int GetCurrentThreadPriority(bool* isRealTime);
|
||||
* 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
|
||||
* @param realTime Set to true to set a real-time priority, false for standard
|
||||
* priority.
|
||||
* @param priority Priority to set the thread to. Scaled 1-99, with 1 being
|
||||
* highest. On RoboRIO, priority is ignored for non realtime
|
||||
* setting.
|
||||
*
|
||||
* @return The success state of setting the 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.
|
||||
* @return True on success.
|
||||
*/
|
||||
bool SetThreadPriority(std::thread& thread, bool realTime, int priority);
|
||||
|
||||
/**
|
||||
* Sets the thread priority for the current thread
|
||||
*
|
||||
* @param realTime Set to true to set a realtime priority, false for standard
|
||||
* @param realTime Set to true to set a real-time priority, false for standard
|
||||
* priority.
|
||||
* @param priority Priority to set the thread to. Scaled 1-99, with 1 being
|
||||
* highest. On RoboRIO, priority is ignored for non realtime
|
||||
* setting.
|
||||
*
|
||||
* @return The success state of setting the 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.
|
||||
* @return True on success.
|
||||
*/
|
||||
bool SetCurrentThreadPriority(bool realTime, int priority);
|
||||
|
||||
|
||||
@@ -98,11 +98,14 @@ class HolonomicDriveController {
|
||||
|
||||
private:
|
||||
Pose2d m_poseError;
|
||||
Rotation2d m_rotationError;
|
||||
Pose2d m_poseTolerance;
|
||||
bool m_enabled = true;
|
||||
|
||||
frc2::PIDController m_xController;
|
||||
frc2::PIDController m_yController;
|
||||
ProfiledPIDController<units::radian> m_thetaController;
|
||||
|
||||
bool m_firstRun = true;
|
||||
};
|
||||
} // namespace frc
|
||||
|
||||
@@ -27,7 +27,7 @@ class PIDController : public frc::Sendable,
|
||||
* @param Ki The integral coefficient.
|
||||
* @param Kd The derivative coefficient.
|
||||
* @param period The period between controller updates in seconds. The
|
||||
* default is 20 milliseconds.
|
||||
* default is 20 milliseconds. Must be non-zero and positive.
|
||||
*/
|
||||
PIDController(double Kp, double Ki, double Kd,
|
||||
units::second_t period = 20_ms);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user