mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
Compare commits
28 Commits
v2023.0.0-
...
v2023.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ed4b3969 | ||
|
|
4a401b89d7 | ||
|
|
c195b4fc46 | ||
|
|
8f2e34c6a3 | ||
|
|
150d692df7 | ||
|
|
3e5bfff1b5 | ||
|
|
9c7e66a27d | ||
|
|
0ca274866b | ||
|
|
dc037f8d41 | ||
|
|
16cdc741cf | ||
|
|
9d5055176d | ||
|
|
d1e66e1296 | ||
|
|
1fc098e696 | ||
|
|
878cc8defb | ||
|
|
8153911160 | ||
|
|
fbdc810887 | ||
|
|
396143004c | ||
|
|
1f45732700 | ||
|
|
574cb41c18 | ||
|
|
d9d6c425e7 | ||
|
|
58b6484dbe | ||
|
|
ca43fe2798 | ||
|
|
87a64ccedc | ||
|
|
89a3d00297 | ||
|
|
1497665f96 | ||
|
|
27b173374e | ||
|
|
2a13dba8ac | ||
|
|
77301b126c |
9
.github/workflows/cmake.yml
vendored
9
.github/workflows/cmake.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
flags: ""
|
||||
- os: macOS-11
|
||||
name: macOS
|
||||
@@ -27,11 +27,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
|
||||
|
||||
- name: Install opencv (macOS)
|
||||
run: brew install opencv
|
||||
if: runner.os == 'macOS'
|
||||
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.8 (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
2
.github/workflows/gazebo.yml
vendored
2
.github/workflows/gazebo.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/gazebo-ubuntu:20.04
|
||||
container: wpilib/gazebo-ubuntu:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
25
.github/workflows/gradle.yml
vendored
25
.github/workflows/gradle.yml
vendored
@@ -12,16 +12,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:10-20.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bionic-20.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:20.04
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
artifact-name: Linux
|
||||
build-options: "-Ponlylinuxx86-64"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
@@ -51,21 +51,29 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2019
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Debug
|
||||
architecture: x64
|
||||
task: "build"
|
||||
build-options: "-PciDebugOnly --max-workers 1"
|
||||
- os: windows-2019
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64Release
|
||||
architecture: x64
|
||||
build-options: "-PciReleaseOnly --max-workers 1"
|
||||
task: "copyAllOutputs"
|
||||
outputs: "build/allOutputs"
|
||||
- os: macOS-11
|
||||
artifact-name: macOS
|
||||
architecture: x64
|
||||
build-options: "-Pbuildalldesktop"
|
||||
task: "build"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win32
|
||||
architecture: x86
|
||||
task: ":ntcoreffi:build"
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -94,6 +102,9 @@ jobs:
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew ${{ matrix.task }} --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
@@ -107,7 +118,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: build/allOutputs
|
||||
path: ${{ matrix.outputs }}
|
||||
|
||||
build-documentation:
|
||||
name: "Build - Documentation"
|
||||
|
||||
4
.github/workflows/lint-format.yml
vendored
4
.github/workflows/lint-format.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
javaformat:
|
||||
name: "Java format"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/ubuntu-base:20.04
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Fetch all history and metadata
|
||||
|
||||
19
.github/workflows/sanitizers.yml
vendored
19
.github/workflows/sanitizers.yml
vendored
@@ -15,34 +15,23 @@ jobs:
|
||||
- name: asan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Asan"
|
||||
ctest-env: ""
|
||||
ctest-flags: "-E 'ntcore|wpilibc'"
|
||||
ctest-flags: "-E 'wpilibc'"
|
||||
- name: tsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
|
||||
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1"
|
||||
ctest-flags: "-E 'ntcore|cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
ctest-flags: "-E 'cscore|cameraserver|wpilibc|wpilibNewCommands'"
|
||||
- name: ubsan
|
||||
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
|
||||
ctest-env: ""
|
||||
ctest-flags: ""
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ubuntu-latest
|
||||
container: wpilib/roborio-cross-ubuntu:2022-20.04
|
||||
container: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt install -y gcc-11 g++-11
|
||||
sudo update-alternatives \
|
||||
--install /usr/bin/gcc gcc /usr/bin/gcc-11 11 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-11
|
||||
sudo update-alternatives --set gcc /usr/bin/gcc-11
|
||||
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
|
||||
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
|
||||
@@ -36,22 +36,23 @@ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
# (but later on when installing)
|
||||
SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
# add the automatically determined parts of the RPATH
|
||||
# which point to directories outside the build tree to the install RPATH
|
||||
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
# the RPATH to be used when installing, but only if it's not a system directory
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/wpilib/lib" isSystemDir)
|
||||
LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
IF("${isSystemDir}" STREQUAL "-1")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/wpilib/lib")
|
||||
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
ENDIF("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
# Options for building certain parts of the repo. Everything is built by default.
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
option(WITH_JAVA "Include java and JNI in the build" ON)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_NTCORE "Build ntcore" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
@@ -63,10 +64,10 @@ option(WITH_SIMULATION_MODULES "Build simulation modules" ON)
|
||||
option(WITH_EXTERNAL_HAL "Use a separately built HAL" OFF)
|
||||
set(EXTERNAL_HAL_FILE "" CACHE FILEPATH "Location to look for an external HAL CMake File")
|
||||
|
||||
# Options for using a package manager (vcpkg) for certain dependencies.
|
||||
option(USE_VCPKG_FMTLIB "Use vcpkg fmtlib" OFF)
|
||||
option(USE_VCPKG_LIBUV "Use vcpkg libuv" OFF)
|
||||
option(USE_VCPKG_EIGEN "Use vcpkg eigen" OFF)
|
||||
# Options for using a package manager (e.g., vcpkg) for certain dependencies.
|
||||
option(USE_SYSTEM_FMTLIB "Use system fmtlib" OFF)
|
||||
option(USE_SYSTEM_LIBUV "Use system libuv" OFF)
|
||||
option(USE_SYSTEM_EIGEN "Use system eigen" OFF)
|
||||
|
||||
# Options for installation.
|
||||
option(WITH_FLAT_INSTALL "Use a flat install directory" OFF)
|
||||
@@ -117,6 +118,34 @@ FATAL: Cannot build simulation modules with wpilib disabled.
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_CSCORE)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build cameraserver without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_GUI)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build GUI modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_SIMULATION_MODULES)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build simulation modules without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_NTCORE AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without ntcore.
|
||||
Enable ntcore by setting WITH_NTCORE=ON
|
||||
")
|
||||
endif()
|
||||
|
||||
if (NOT WITH_WPIMATH AND WITH_WPILIB)
|
||||
message(FATAL_ERROR "
|
||||
FATAL: Cannot build wpilib without wpimath.
|
||||
@@ -124,11 +153,11 @@ FATAL: Cannot build wpilib without wpimath.
|
||||
")
|
||||
endif()
|
||||
|
||||
set( wpilib_dest wpilib)
|
||||
set( include_dest wpilib/include )
|
||||
set( main_lib_dest wpilib/lib )
|
||||
set( java_lib_dest wpilib/java )
|
||||
set( jni_lib_dest wpilib/jni )
|
||||
set( wpilib_dest "")
|
||||
set( include_dest include )
|
||||
set( main_lib_dest lib )
|
||||
set( java_lib_dest java )
|
||||
set( jni_lib_dest jni )
|
||||
|
||||
if (WITH_FLAT_INSTALL)
|
||||
set (wpilib_config_dir ${wpilib_dest})
|
||||
@@ -136,12 +165,15 @@ else()
|
||||
set (wpilib_config_dir share/wpilib)
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_LIBUV)
|
||||
set (LIBUV_VCPKG_REPLACE "find_package(unofficial-libuv CONFIG)")
|
||||
if (USE_SYSTEM_LIBUV)
|
||||
set (LIBUV_SYSTEM_REPLACE "
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(libuv REQUIRED IMPORTED_TARGET libuv)
|
||||
")
|
||||
endif()
|
||||
|
||||
if (USE_VCPKG_EIGEN)
|
||||
set (EIGEN_VCPKG_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
if (USE_SYSTEM_EIGEN)
|
||||
set (EIGEN_SYSTEM_REPLACE "find_package(Eigen3 CONFIG)")
|
||||
endif()
|
||||
|
||||
find_package(LIBSSH 0.7.1)
|
||||
@@ -247,8 +279,11 @@ if (WITH_TESTS)
|
||||
endif()
|
||||
|
||||
add_subdirectory(wpiutil)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
|
||||
if (WITH_NTCORE)
|
||||
add_subdirectory(wpinet)
|
||||
add_subdirectory(ntcore)
|
||||
endif()
|
||||
|
||||
if (WITH_WPIMATH)
|
||||
add_subdirectory(wpimath)
|
||||
|
||||
@@ -31,14 +31,20 @@ The following build options are available:
|
||||
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
|
||||
* `WITH_SHARED_LIBS` (ON Default)
|
||||
* This option will cause cmake to build static libraries instead of shared libraries. If this is off, `WITH_JAVA` must be off. Otherwise CMake will error.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_CSCORE` (ON Default)
|
||||
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver, the hal and wpilib as well, irrespective of their specific options. If this is off, the OpenCV build requirement is removed.
|
||||
* `WITH_NTCORE` (ON Default)
|
||||
* This option will cause ntcore to be built. Turning this off will implicitly disable wpinet and wpilib as well, irrespective of their specific options.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_WPILIB` (ON Default)
|
||||
* This option will build the hal and wpilibc/j during the build. The HAL is the simulator hal, unless the external hal options are used. The cmake build has no capability to build for the RoboRIO.
|
||||
* `WITH_EXAMPLES` (ON Default)
|
||||
* This option will build C++ examples.
|
||||
* `WITH_TESTS` (ON Default)
|
||||
* This option will build C++ unit tests. These can be run via `make test`.
|
||||
* `WITH_GUI` (ON Default)
|
||||
* This option will build GUI items.
|
||||
* `WITH_SIMULATION_MODULES` (ON Default)
|
||||
* This option will build simulation modules, including wpigui and the HALSim plugins.
|
||||
* `WITH_EXTERNAL_HAL` (OFF Default)
|
||||
|
||||
@@ -47,8 +47,8 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
|
||||
- On Windows, install the JDK 11 .msi from the link above
|
||||
- On macOS, install the JDK 11 .pkg from the link above
|
||||
- C++ compiler
|
||||
- On Linux, install GCC 8 or greater
|
||||
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On Linux, install GCC 11 or greater
|
||||
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
|
||||
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
|
||||
- ARM compiler toolchain
|
||||
- Run `./gradlew installRoboRioToolchain` after cloning this repository
|
||||
|
||||
@@ -34,8 +34,6 @@ popper.js wpinet/src/main/native/resources/popper-*
|
||||
units wpimath/src/main/native/include/units/
|
||||
Eigen wpimath/src/main/native/thirdparty/eigen/include/
|
||||
StackWalker wpiutil/src/main/native/windows/StackWalker.*
|
||||
TCB span wpiutil/src/main/native/thirdparty/include/wpi/span.h
|
||||
wpiutil/src/test/native/cpp/span/
|
||||
GHC filesystem wpiutil/src/main/native/thirdparty/include/wpi/ghc/
|
||||
Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineParameterizer.java
|
||||
wpilibj/src/main/java/edu/wpi/first/wpilibj/trajectory/TrajectoryParameterizer.java
|
||||
|
||||
@@ -15,7 +15,7 @@ stages:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
container:
|
||||
image: wpilib/roborio-cross-ubuntu:2022-18.04
|
||||
image: wpilib/roborio-cross-ubuntu:2023-22.04
|
||||
|
||||
timeoutInMinutes: 0
|
||||
|
||||
|
||||
@@ -108,6 +108,11 @@ subprojects {
|
||||
subproj.apply plugin: MultiBuilds
|
||||
}
|
||||
|
||||
plugins.withType(JavaPlugin) {
|
||||
sourceCompatibility = 11
|
||||
targetCompatibility = 11
|
||||
}
|
||||
|
||||
apply from: "${rootDir}/shared/java/javastyle.gradle"
|
||||
|
||||
// Disables doclint in java 8.
|
||||
@@ -119,6 +124,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add '-XDstringConcat=inline'
|
||||
}
|
||||
|
||||
// Enables UTF-8 support in Javadoc
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption("charset", "utf-8")
|
||||
|
||||
@@ -5,5 +5,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2023.1.0"
|
||||
implementation "edu.wpi.first:native-utils:2023.2.7"
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ model {
|
||||
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
|
||||
return
|
||||
}
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
project(':ntcore').addNtcoreDependency(it, 'shared')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'shared'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
|
||||
@@ -55,7 +55,7 @@ model {
|
||||
}
|
||||
binaries.all { binary ->
|
||||
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
project(':ntcore').addNtcoreDependency(binary, 'static')
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
|
||||
@@ -175,7 +175,8 @@ public final class Main {
|
||||
ntinst.startServer();
|
||||
} else {
|
||||
System.out.println("Setting up NetworkTables client for team " + team);
|
||||
ntinst.startClientTeam(team);
|
||||
ntinst.setServerTeam(team);
|
||||
ntinst.startClient4("multicameraserver");
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
@@ -182,7 +183,8 @@ int main(int argc, char* argv[]) {
|
||||
ntinst.StartServer();
|
||||
} else {
|
||||
fmt::print("Setting up NetworkTables client for team {}\n", team);
|
||||
ntinst.StartClientTeam(team);
|
||||
ntinst.StartClient4("multicameraserver");
|
||||
ntinst.SetServerTeam(team);
|
||||
}
|
||||
|
||||
// start cameras
|
||||
|
||||
@@ -15,13 +15,18 @@ import edu.wpi.first.cscore.VideoException;
|
||||
import edu.wpi.first.cscore.VideoListener;
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import edu.wpi.first.cscore.VideoMode.PixelFormat;
|
||||
import edu.wpi.first.cscore.VideoProperty;
|
||||
import edu.wpi.first.cscore.VideoSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.EntryListenerFlags;
|
||||
import edu.wpi.first.networktables.BooleanEntry;
|
||||
import edu.wpi.first.networktables.BooleanPublisher;
|
||||
import edu.wpi.first.networktables.IntegerEntry;
|
||||
import edu.wpi.first.networktables.IntegerPublisher;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import edu.wpi.first.networktables.StringArrayPublisher;
|
||||
import edu.wpi.first.networktables.StringArrayTopic;
|
||||
import edu.wpi.first.networktables.StringEntry;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -38,11 +43,167 @@ public final class CameraServer {
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
|
||||
private static final class PropertyPublisher implements AutoCloseable {
|
||||
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.ImplicitSwitchFallThrough", "fallthrough"})
|
||||
PropertyPublisher(NetworkTable table, VideoEvent event) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
m_booleanValueEntry = table.getBooleanTopic(name).getEntry(false);
|
||||
m_booleanValueEntry.setDefault(event.value != 0);
|
||||
break;
|
||||
case kEnum:
|
||||
m_choicesTopic = table.getStringArrayTopic(infoName + "/choices");
|
||||
// fall through
|
||||
case kInteger:
|
||||
m_integerValueEntry = table.getIntegerTopic(name).getEntry(0);
|
||||
m_minPublisher = table.getIntegerTopic(infoName + "/min").publish();
|
||||
m_maxPublisher = table.getIntegerTopic(infoName + "/max").publish();
|
||||
m_stepPublisher = table.getIntegerTopic(infoName + "/step").publish();
|
||||
m_defaultPublisher = table.getIntegerTopic(infoName + "/default").publish();
|
||||
|
||||
m_integerValueEntry.setDefault(event.value);
|
||||
m_minPublisher.set(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
m_maxPublisher.set(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
m_stepPublisher.set(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
m_defaultPublisher.set(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
break;
|
||||
case kString:
|
||||
m_stringValueEntry = table.getStringTopic(name).getEntry("");
|
||||
m_stringValueEntry.setDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
void update(VideoEvent event) {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.set(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.close();
|
||||
}
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.close();
|
||||
}
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.close();
|
||||
}
|
||||
if (m_minPublisher != null) {
|
||||
m_minPublisher.close();
|
||||
}
|
||||
if (m_maxPublisher != null) {
|
||||
m_maxPublisher.close();
|
||||
}
|
||||
if (m_stepPublisher != null) {
|
||||
m_stepPublisher.close();
|
||||
}
|
||||
if (m_defaultPublisher != null) {
|
||||
m_defaultPublisher.close();
|
||||
}
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
IntegerEntry m_integerValueEntry;
|
||||
StringEntry m_stringValueEntry;
|
||||
IntegerPublisher m_minPublisher;
|
||||
IntegerPublisher m_maxPublisher;
|
||||
IntegerPublisher m_stepPublisher;
|
||||
IntegerPublisher m_defaultPublisher;
|
||||
StringArrayTopic m_choicesTopic;
|
||||
StringArrayPublisher m_choicesPublisher;
|
||||
}
|
||||
|
||||
private static final class SourcePublisher implements AutoCloseable {
|
||||
SourcePublisher(NetworkTable table, int sourceHandle) {
|
||||
this.m_table = table;
|
||||
m_sourcePublisher = table.getStringTopic("source").publish();
|
||||
m_descriptionPublisher = table.getStringTopic("description").publish();
|
||||
m_connectedPublisher = table.getBooleanTopic("connected").publish();
|
||||
m_streamsPublisher = table.getStringArrayTopic("streams").publish();
|
||||
m_modeEntry = table.getStringTopic("mode").getEntry("");
|
||||
m_modesPublisher = table.getStringArrayTopic("modes").publish();
|
||||
|
||||
m_sourcePublisher.set(makeSourceValue(sourceHandle));
|
||||
m_descriptionPublisher.set(CameraServerJNI.getSourceDescription(sourceHandle));
|
||||
m_connectedPublisher.set(CameraServerJNI.isSourceConnected(sourceHandle));
|
||||
m_streamsPublisher.set(getSourceStreamValues(sourceHandle));
|
||||
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(sourceHandle);
|
||||
m_modeEntry.setDefault(videoModeToString(mode));
|
||||
m_modesPublisher.set(getSourceModeValues(sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
m_sourcePublisher.close();
|
||||
m_descriptionPublisher.close();
|
||||
m_connectedPublisher.close();
|
||||
m_streamsPublisher.close();
|
||||
m_modeEntry.close();
|
||||
m_modesPublisher.close();
|
||||
for (PropertyPublisher pp : m_properties.values()) {
|
||||
pp.close();
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkTable m_table;
|
||||
final StringPublisher m_sourcePublisher;
|
||||
final StringPublisher m_descriptionPublisher;
|
||||
final BooleanPublisher m_connectedPublisher;
|
||||
final StringArrayPublisher m_streamsPublisher;
|
||||
final StringEntry m_modeEntry;
|
||||
final StringArrayPublisher m_modesPublisher;
|
||||
final Map<Integer, PropertyPublisher> m_properties = new HashMap<>();
|
||||
}
|
||||
|
||||
private static final AtomicInteger m_defaultUsbDevice = new AtomicInteger();
|
||||
private static String m_primarySourceName;
|
||||
private static final Map<String, VideoSource> m_sources = new HashMap<>();
|
||||
private static final Map<String, VideoSink> m_sinks = new HashMap<>();
|
||||
private static final Map<Integer, NetworkTable> m_tables =
|
||||
private static final Map<Integer, SourcePublisher> m_publishers =
|
||||
new HashMap<>(); // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private static final Map<Integer, Integer> m_fixedSources = new HashMap<>();
|
||||
@@ -61,189 +222,124 @@ public final class CameraServer {
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings("PMD.UnusedPrivateField")
|
||||
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_tables.put(event.sourceHandle, table);
|
||||
table.getEntry("source").setString(makeSourceValue(event.sourceHandle));
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table
|
||||
.getEntry("connected")
|
||||
.setBoolean(CameraServerJNI.isSourceConnected(event.sourceHandle));
|
||||
table
|
||||
.getEntry("streams")
|
||||
.setStringArray(getSourceStreamValues(event.sourceHandle));
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(event.sourceHandle);
|
||||
table.getEntry("mode").setDefaultString(videoModeToString(mode));
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
synchronized (CameraServer.class) {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated:
|
||||
{
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_publishers.put(
|
||||
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("source").setString("");
|
||||
table.getEntry("streams").setStringArray(new String[0]);
|
||||
table.getEntry("modes").setStringArray(new String[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
// update the description too (as it may have changed)
|
||||
table
|
||||
.getEntry("description")
|
||||
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
table.getEntry("connected").setBoolean(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("connected").setBoolean(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
table.getEntry("mode").setString(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
putSourcePropertyValue(table, event, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
NetworkTable table = m_tables.get(event.sourceHandle);
|
||||
if (table != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
table
|
||||
.getEntry("PropertyInfo/" + event.name + "/choices")
|
||||
.setStringArray(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
case kSourceDestroyed:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
try {
|
||||
publisher.close();
|
||||
} catch (Exception e) {
|
||||
// ignore (nothing we can do about it)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceConnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
// update the description too (as it may have changed)
|
||||
publisher.m_descriptionPublisher.set(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
publisher.m_connectedPublisher.set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceDisconnected:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_connectedPublisher.set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourceVideoModeChanged:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modeEntry.set(videoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyCreated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_properties.put(
|
||||
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyValueUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null) {
|
||||
pp.update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated:
|
||||
{
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null && pp.m_choicesTopic != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
if (pp.m_choicesPublisher == null) {
|
||||
pp.m_choicesPublisher = pp.m_choicesTopic.publish();
|
||||
}
|
||||
pp.m_choicesPublisher.set(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore (just don't publish choices if we can't get them)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case kSinkSourceChanged:
|
||||
case kSinkCreated:
|
||||
case kSinkDestroyed:
|
||||
case kNetworkInterfacesChanged:
|
||||
{
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
0x4fff,
|
||||
true);
|
||||
|
||||
@SuppressWarnings("PMD.UnusedPrivateField")
|
||||
private static final int m_tableListener =
|
||||
NetworkTableInstance.getDefault()
|
||||
.addEntryListener(
|
||||
kPublishName + "/",
|
||||
event -> {
|
||||
String relativeKey = event.name.substring(kPublishName.length() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
int subKeyIndex = relativeKey.indexOf('/');
|
||||
if (subKeyIndex == -1) {
|
||||
return;
|
||||
}
|
||||
String sourceName = relativeKey.substring(0, subKeyIndex);
|
||||
VideoSource source = m_sources.get(sourceName);
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey = relativeKey.substring(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
String propName;
|
||||
if ("mode".equals(relativeKey)) {
|
||||
// reset to current mode
|
||||
event.getEntry().setString(videoModeToString(source.getVideoMode()));
|
||||
return;
|
||||
} else if (relativeKey.startsWith("Property/")) {
|
||||
propName = relativeKey.substring(9);
|
||||
} else if (relativeKey.startsWith("RawProperty/")) {
|
||||
propName = relativeKey.substring(12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
VideoProperty property = source.getProperty(propName);
|
||||
switch (property.getKind()) {
|
||||
case kNone:
|
||||
return;
|
||||
case kBoolean:
|
||||
// reset to current setting
|
||||
event.getEntry().setBoolean(property.get() != 0);
|
||||
return;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
// reset to current setting
|
||||
event.getEntry().setDouble(property.get());
|
||||
return;
|
||||
case kString:
|
||||
// reset to current setting
|
||||
event.getEntry().setString(property.getString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
EntryListenerFlags.kImmediate | EntryListenerFlags.kUpdate);
|
||||
|
||||
private static int m_nextPort = kBasePort;
|
||||
private static String[] m_addresses = new String[0];
|
||||
|
||||
@@ -369,8 +465,8 @@ public final class CameraServer {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
== VideoSource.Kind.kHttp) {
|
||||
@@ -380,7 +476,7 @@ public final class CameraServer {
|
||||
// Set table value
|
||||
String[] values = getSinkStreamValues(sink);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,12 +486,12 @@ public final class CameraServer {
|
||||
int source = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
NetworkTable table = m_tables.get(source);
|
||||
if (table != null) {
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Set table value
|
||||
String[] values = getSourceStreamValues(source);
|
||||
if (values.length > 0) {
|
||||
table.getEntry("streams").setStringArray(values);
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,69 +545,6 @@ public final class CameraServer {
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a source property value to NetworkTables.
|
||||
*
|
||||
* @param table NetworkTable to which to push value.
|
||||
* @param event Video event.
|
||||
* @param isNew Whether the property value hasn't been pushed to NetworkTables before.
|
||||
*/
|
||||
private static void putSourcePropertyValue(NetworkTable table, VideoEvent event, boolean isNew) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
NetworkTableEntry entry = table.getEntry(name);
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
if (isNew) {
|
||||
entry.setDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.setBoolean(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case kInteger:
|
||||
case kEnum:
|
||||
if (isNew) {
|
||||
entry.setDefaultDouble(event.value);
|
||||
table
|
||||
.getEntry(infoName + "/min")
|
||||
.setDouble(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/max")
|
||||
.setDouble(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/step")
|
||||
.setDouble(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
table
|
||||
.getEntry(infoName + "/default")
|
||||
.setDouble(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
} else {
|
||||
entry.setDouble(event.value);
|
||||
}
|
||||
break;
|
||||
case kString:
|
||||
if (isNew) {
|
||||
entry.setDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.setString(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private CameraServer() {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/IntegerTopic.h>
|
||||
#include <networktables/NetworkTable.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringArrayTopic.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
@@ -24,9 +28,42 @@ using namespace frc;
|
||||
static constexpr char const* kPublishName = "/CameraPublisher";
|
||||
|
||||
namespace {
|
||||
|
||||
struct Instance;
|
||||
|
||||
struct PropertyPublisher {
|
||||
PropertyPublisher(nt::NetworkTable& table, const cs::VideoEvent& event);
|
||||
|
||||
void Update(const cs::VideoEvent& event);
|
||||
|
||||
nt::BooleanEntry booleanValueEntry;
|
||||
nt::IntegerEntry integerValueEntry;
|
||||
nt::StringEntry stringValueEntry;
|
||||
nt::IntegerPublisher minPublisher;
|
||||
nt::IntegerPublisher maxPublisher;
|
||||
nt::IntegerPublisher stepPublisher;
|
||||
nt::IntegerPublisher defaultPublisher;
|
||||
nt::StringArrayTopic choicesTopic;
|
||||
nt::StringArrayPublisher choicesPublisher;
|
||||
};
|
||||
|
||||
struct SourcePublisher {
|
||||
SourcePublisher(Instance& inst, std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source);
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> table;
|
||||
nt::StringPublisher sourcePublisher;
|
||||
nt::StringPublisher descriptionPublisher;
|
||||
nt::BooleanPublisher connectedPublisher;
|
||||
nt::StringArrayPublisher streamsPublisher;
|
||||
nt::StringEntry modeEntry;
|
||||
nt::StringArrayPublisher modesPublisher;
|
||||
wpi::DenseMap<CS_Property, PropertyPublisher> properties;
|
||||
};
|
||||
|
||||
struct Instance {
|
||||
Instance();
|
||||
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
|
||||
SourcePublisher* GetPublisher(CS_Source source);
|
||||
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
|
||||
std::vector<std::string> GetSourceStreamValues(CS_Source source);
|
||||
void UpdateStreamValues();
|
||||
@@ -37,7 +74,7 @@ struct Instance {
|
||||
wpi::StringMap<cs::VideoSource> m_sources;
|
||||
wpi::StringMap<cs::VideoSink> m_sinks;
|
||||
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
|
||||
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
|
||||
wpi::DenseMap<CS_Source, SourcePublisher> m_publishers;
|
||||
std::shared_ptr<nt::NetworkTable> m_publishTable{
|
||||
nt::NetworkTableInstance::GetDefault().GetTable(kPublishName)};
|
||||
cs::VideoListener m_videoListener;
|
||||
@@ -45,6 +82,7 @@ struct Instance {
|
||||
int m_nextPort{CameraServer::kBasePort};
|
||||
std::vector<std::string> m_addresses;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static Instance& GetInstance() {
|
||||
@@ -86,9 +124,13 @@ static std::string MakeStreamValue(std::string_view address, int port) {
|
||||
return fmt::format("mjpg:http://{}:{}/?action=stream", address, port);
|
||||
}
|
||||
|
||||
std::shared_ptr<nt::NetworkTable> Instance::GetSourceTable(CS_Source source) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
return m_tables.lookup(source);
|
||||
SourcePublisher* Instance::GetPublisher(CS_Source source) {
|
||||
auto it = m_publishers.find(source);
|
||||
if (it != m_publishers.end()) {
|
||||
return &it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Instance::GetSinkStreamValues(CS_Sink sink) {
|
||||
@@ -158,7 +200,6 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
|
||||
}
|
||||
|
||||
void Instance::UpdateStreamValues() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
// Over all the sinks...
|
||||
for (const auto& i : m_sinks) {
|
||||
CS_Status status = 0;
|
||||
@@ -172,8 +213,7 @@ void Instance::UpdateStreamValues() {
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (cs::GetSourceKind(source, &status) == CS_SOURCE_HTTP) {
|
||||
continue;
|
||||
@@ -182,7 +222,7 @@ void Instance::UpdateStreamValues() {
|
||||
// Set table value
|
||||
auto values = GetSinkStreamValues(sink);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,12 +232,11 @@ void Instance::UpdateStreamValues() {
|
||||
CS_Source source = i.second.GetHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
auto table = m_tables.lookup(source);
|
||||
if (table) {
|
||||
if (auto publisher = GetPublisher(source)) {
|
||||
// Set table value
|
||||
auto values = GetSourceStreamValues(source);
|
||||
if (!values.empty()) {
|
||||
table->GetEntry("streams").SetStringArray(values);
|
||||
publisher->streamsPublisher.Set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,51 +273,71 @@ static std::vector<std::string> GetSourceModeValues(int source) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
const cs::VideoEvent& event, bool isNew) {
|
||||
std::string_view namePrefix;
|
||||
std::string_view infoPrefix;
|
||||
PropertyPublisher::PropertyPublisher(nt::NetworkTable& table,
|
||||
const cs::VideoEvent& event) {
|
||||
std::string name;
|
||||
std::string infoName;
|
||||
if (wpi::starts_with(event.name, "raw_")) {
|
||||
namePrefix = "RawProperty";
|
||||
infoPrefix = "RawPropertyInfo";
|
||||
name = fmt::format("RawProperty/{}", event.name);
|
||||
infoName = fmt::format("RawPropertyInfo/{}", event.name);
|
||||
} else {
|
||||
namePrefix = "Property";
|
||||
infoPrefix = "PropertyInfo";
|
||||
name = fmt::format("Property/{}", event.name);
|
||||
infoName = fmt::format("PropertyInfo/{}", event.name);
|
||||
}
|
||||
|
||||
wpi::SmallString<64> buf;
|
||||
CS_Status status = 0;
|
||||
nt::NetworkTableEntry entry =
|
||||
table->GetEntry(fmt::format("{}/{}", namePrefix, event.name));
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (isNew) {
|
||||
entry.SetDefaultBoolean(event.value != 0);
|
||||
} else {
|
||||
entry.SetBoolean(event.value != 0);
|
||||
booleanValueEntry = table.GetBooleanTopic(name).GetEntry(false);
|
||||
booleanValueEntry.SetDefault(event.value != 0);
|
||||
break;
|
||||
case CS_PROP_ENUM:
|
||||
choicesTopic =
|
||||
table.GetStringArrayTopic(fmt::format("{}/choices", infoName));
|
||||
[[fallthrough]];
|
||||
case CS_PROP_INTEGER:
|
||||
integerValueEntry = table.GetIntegerTopic(name).GetEntry(0);
|
||||
minPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/min", infoName)).Publish();
|
||||
maxPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/max", infoName)).Publish();
|
||||
stepPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/step", infoName)).Publish();
|
||||
defaultPublisher =
|
||||
table.GetIntegerTopic(fmt::format("{}/default", infoName)).Publish();
|
||||
|
||||
integerValueEntry.SetDefault(event.value);
|
||||
minPublisher.Set(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
maxPublisher.Set(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
stepPublisher.Set(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
defaultPublisher.Set(
|
||||
cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
stringValueEntry = table.GetStringTopic(name).GetEntry("");
|
||||
stringValueEntry.SetDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPublisher::Update(const cs::VideoEvent& event) {
|
||||
switch (event.propertyKind) {
|
||||
case CS_PROP_BOOLEAN:
|
||||
if (booleanValueEntry) {
|
||||
booleanValueEntry.Set(event.value != 0);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_INTEGER:
|
||||
case CS_PROP_ENUM:
|
||||
if (isNew) {
|
||||
entry.SetDefaultDouble(event.value);
|
||||
table->GetEntry(fmt::format("{}/{}/min", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMin(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/max", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyMax(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/step", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyStep(event.propertyHandle, &status));
|
||||
table->GetEntry(fmt::format("{}/{}/default", infoPrefix, event.name))
|
||||
.SetDouble(cs::GetPropertyDefault(event.propertyHandle, &status));
|
||||
} else {
|
||||
entry.SetDouble(event.value);
|
||||
if (integerValueEntry) {
|
||||
integerValueEntry.Set(event.value);
|
||||
}
|
||||
break;
|
||||
case CS_PROP_STRING:
|
||||
if (isNew) {
|
||||
entry.SetDefaultString(event.valueStr);
|
||||
} else {
|
||||
entry.SetString(event.valueStr);
|
||||
if (stringValueEntry) {
|
||||
stringValueEntry.Set(event.valueStr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -286,6 +345,28 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
|
||||
}
|
||||
}
|
||||
|
||||
SourcePublisher::SourcePublisher(Instance& inst,
|
||||
std::shared_ptr<nt::NetworkTable> table,
|
||||
CS_Source source)
|
||||
: table{table},
|
||||
sourcePublisher{table->GetStringTopic("source").Publish()},
|
||||
descriptionPublisher{table->GetStringTopic("description").Publish()},
|
||||
connectedPublisher{table->GetBooleanTopic("connected").Publish()},
|
||||
streamsPublisher{table->GetStringArrayTopic("streams").Publish()},
|
||||
modeEntry{table->GetStringTopic("mode").GetEntry("")},
|
||||
modesPublisher{table->GetStringArrayTopic("modes").Publish()} {
|
||||
CS_Status status = 0;
|
||||
wpi::SmallString<64> buf;
|
||||
sourcePublisher.Set(MakeSourceValue(source, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
descriptionPublisher.Set(cs::GetSourceDescription(source, descBuf, &status));
|
||||
connectedPublisher.Set(cs::IsSourceConnected(source, &status));
|
||||
streamsPublisher.Set(inst.GetSourceStreamValues(source));
|
||||
auto mode = cs::GetSourceVideoMode(source, &status);
|
||||
modeEntry.SetDefault(VideoModeToString(mode));
|
||||
modesPublisher.Set(GetSourceModeValues(source));
|
||||
}
|
||||
|
||||
Instance::Instance() {
|
||||
// We publish sources to NetworkTables using the following structure:
|
||||
// "/CameraPublisher/{Source.Name}/" - root
|
||||
@@ -300,177 +381,88 @@ Instance::Instance() {
|
||||
|
||||
// Listener for video events
|
||||
m_videoListener = cs::VideoListener{
|
||||
[=](const cs::VideoEvent& event) {
|
||||
[=, this](const cs::VideoEvent& event) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
CS_Status status = 0;
|
||||
switch (event.kind) {
|
||||
case cs::VideoEvent::kSourceCreated: {
|
||||
// Create subtable for the camera
|
||||
auto table = m_publishTable->GetSubTable(event.name);
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_tables.insert(std::make_pair(event.sourceHandle, table));
|
||||
}
|
||||
wpi::SmallString<64> buf;
|
||||
table->GetEntry("source").SetString(
|
||||
MakeSourceValue(event.sourceHandle, buf));
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle, descBuf,
|
||||
&status));
|
||||
table->GetEntry("connected")
|
||||
.SetBoolean(cs::IsSourceConnected(event.sourceHandle, &status));
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
GetSourceStreamValues(event.sourceHandle));
|
||||
auto mode = cs::GetSourceVideoMode(event.sourceHandle, &status);
|
||||
table->GetEntry("mode").SetDefaultString(VideoModeToString(mode));
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
m_publishers.insert(
|
||||
{event.sourceHandle,
|
||||
SourcePublisher{*this, table, event.sourceHandle}});
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("source").SetString("");
|
||||
table->GetEntry("streams").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
std::vector<std::string>{});
|
||||
}
|
||||
case cs::VideoEvent::kSourceDestroyed:
|
||||
m_publishers.erase(event.sourceHandle);
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceConnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
case cs::VideoEvent::kSourceConnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
// update the description too (as it may have changed)
|
||||
wpi::SmallString<64> descBuf;
|
||||
table->GetEntry("description")
|
||||
.SetString(cs::GetSourceDescription(event.sourceHandle,
|
||||
descBuf, &status));
|
||||
table->GetEntry("connected").SetBoolean(true);
|
||||
publisher->descriptionPublisher.Set(cs::GetSourceDescription(
|
||||
event.sourceHandle, descBuf, &status));
|
||||
publisher->connectedPublisher.Set(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceDisconnected: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("connected").SetBoolean(false);
|
||||
case cs::VideoEvent::kSourceDisconnected:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->connectedPublisher.Set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("modes").SetStringArray(
|
||||
case cs::VideoEvent::kSourceVideoModesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modesPublisher.Set(
|
||||
GetSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourceVideoModeChanged: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
table->GetEntry("mode").SetString(VideoModeToString(event.mode));
|
||||
case cs::VideoEvent::kSourceVideoModeChanged:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->modeEntry.Set(VideoModeToString(event.mode));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyCreated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, true);
|
||||
case cs::VideoEvent::kSourcePropertyCreated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
publisher->properties.insert(
|
||||
{event.propertyHandle,
|
||||
PropertyPublisher{*publisher->table, event}});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
PutSourcePropertyValue(table.get(), event, false);
|
||||
case cs::VideoEvent::kSourcePropertyValueUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end()) {
|
||||
ppIt->second.Update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated: {
|
||||
auto table = GetSourceTable(event.sourceHandle);
|
||||
if (table) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
table
|
||||
->GetEntry(fmt::format("PropertyInfo/{}/choices", event.name))
|
||||
.SetStringArray(choices);
|
||||
case cs::VideoEvent::kSourcePropertyChoicesUpdated:
|
||||
if (auto publisher = GetPublisher(event.sourceHandle)) {
|
||||
auto ppIt = publisher->properties.find(event.propertyHandle);
|
||||
if (ppIt != publisher->properties.end() &&
|
||||
ppIt->second.choicesTopic) {
|
||||
auto choices =
|
||||
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
|
||||
if (!ppIt->second.choicesPublisher) {
|
||||
ppIt->second.choicesPublisher =
|
||||
ppIt->second.choicesTopic.Publish();
|
||||
}
|
||||
ppIt->second.choicesPublisher.Set(choices);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cs::VideoEvent::kSinkSourceChanged:
|
||||
case cs::VideoEvent::kSinkCreated:
|
||||
case cs::VideoEvent::kSinkDestroyed:
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged: {
|
||||
case cs::VideoEvent::kNetworkInterfacesChanged:
|
||||
m_addresses = cs::GetNetworkInterfaces();
|
||||
UpdateStreamValues();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
0x4fff, true};
|
||||
|
||||
// Listener for NetworkTable events
|
||||
// We don't currently support changing settings via NT due to
|
||||
// synchronization issues, so just update to current setting if someone
|
||||
// else tries to change it.
|
||||
wpi::SmallString<64> buf;
|
||||
m_tableListener = nt::NetworkTableInstance::GetDefault().AddEntryListener(
|
||||
fmt::format("{}/", kPublishName),
|
||||
[=](const nt::EntryNotification& event) {
|
||||
auto relativeKey = wpi::drop_front(
|
||||
event.name, std::string_view{kPublishName}.size() + 1);
|
||||
|
||||
// get source (sourceName/...)
|
||||
auto subKeyIndex = relativeKey.find('/');
|
||||
if (subKeyIndex == std::string_view::npos) {
|
||||
return;
|
||||
}
|
||||
auto sourceName = wpi::slice(relativeKey, 0, subKeyIndex);
|
||||
auto sourceIt = m_sources.find(sourceName);
|
||||
if (sourceIt == m_sources.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get subkey
|
||||
relativeKey.remove_prefix(subKeyIndex + 1);
|
||||
|
||||
// handle standard names
|
||||
std::string_view propName;
|
||||
nt::NetworkTableEntry entry{event.entry};
|
||||
if (relativeKey == "mode") {
|
||||
// reset to current mode
|
||||
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
|
||||
return;
|
||||
} else if (wpi::starts_with(relativeKey, "Property/")) {
|
||||
propName = wpi::substr(relativeKey, 9);
|
||||
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
|
||||
propName = wpi::substr(relativeKey, 12);
|
||||
} else {
|
||||
return; // ignore
|
||||
}
|
||||
|
||||
// everything else is a property
|
||||
auto property = sourceIt->second.GetProperty(propName);
|
||||
switch (property.GetKind()) {
|
||||
case cs::VideoProperty::kNone:
|
||||
return;
|
||||
case cs::VideoProperty::kBoolean:
|
||||
entry.SetBoolean(property.Get() != 0);
|
||||
return;
|
||||
case cs::VideoProperty::kInteger:
|
||||
case cs::VideoProperty::kEnum:
|
||||
entry.SetDouble(property.Get());
|
||||
return;
|
||||
case cs::VideoProperty::kString:
|
||||
entry.SetString(property.GetString());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
NT_NOTIFY_IMMEDIATE | NT_NOTIFY_UPDATE);
|
||||
}
|
||||
|
||||
cs::UsbCamera CameraServer::StartAutomaticCapture() {
|
||||
@@ -519,7 +511,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
|
||||
return AddAxisCamera("Axis Camera", host);
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(wpi::span<const std::string> hosts) {
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
@@ -551,7 +543,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
}
|
||||
|
||||
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
cs::AxisCamera camera{name, hosts};
|
||||
StartAutomaticCapture(camera);
|
||||
auto csShared = GetCameraServerShared();
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
|
||||
@@ -110,7 +109,7 @@ class CameraServer {
|
||||
*
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(wpi::span<const std::string> hosts);
|
||||
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -155,7 +154,7 @@ class CameraServer {
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts);
|
||||
std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
|
||||
@@ -49,6 +49,7 @@ class TestEnvironment : public testing::Environment {
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.enabled && controlWord.dsAttached;
|
||||
};
|
||||
HAL_RefreshDSData();
|
||||
while (!checkEnabled()) {
|
||||
if (enableCounter > 50) {
|
||||
// Robot did not enable properly after 5 seconds.
|
||||
@@ -60,6 +61,7 @@ class TestEnvironment : public testing::Environment {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
fmt::print("Waiting for enable: {}\n", enableCounter++);
|
||||
HAL_RefreshDSData();
|
||||
}
|
||||
std::this_thread::sleep_for(500ms);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ int ConfigurableSourceImpl::CreateProperty(
|
||||
}
|
||||
|
||||
void ConfigurableSourceImpl::SetEnumPropertyChoices(
|
||||
int property, wpi::span<const std::string> choices, CS_Status* status) {
|
||||
int property, std::span<const std::string> choices, CS_Status* status) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
auto prop = GetProperty(property);
|
||||
if (!prop) {
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -42,7 +41,7 @@ class ConfigurableSourceImpl : public SourceImpl {
|
||||
int maximum, int step, int defaultValue, int value,
|
||||
std::function<void(CS_Property property)> onChange);
|
||||
void SetEnumPropertyChoices(int property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
|
||||
private:
|
||||
|
||||
@@ -110,7 +110,7 @@ void CvSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -139,7 +139,7 @@ CS_Property CreateSourcePropertyCallback(
|
||||
}
|
||||
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
if (!data || (data->kind & SourceMask) == 0) {
|
||||
|
||||
@@ -75,7 +75,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
std::unique_lock lock(m_mutex);
|
||||
// sleep for 1 second between checks
|
||||
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
|
||||
[=] { return !m_active; });
|
||||
[=, this] { return !m_active; });
|
||||
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -85,7 +85,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
// (this will result in an error at the read point, and ultimately
|
||||
// a reconnect attempt)
|
||||
if (m_streamConn && m_frameCount == 0) {
|
||||
SWARNING("{}", "Monitor detected stream hung, disconnecting");
|
||||
SWARNING("Monitor detected stream hung, disconnecting");
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void HttpCameraImpl::MonitorThreadMain() {
|
||||
m_frameCount = 0;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Monitor Thread exiting");
|
||||
SDEBUG("Monitor Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::StreamThreadMain() {
|
||||
@@ -110,7 +110,8 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
m_streamConn->stream->close();
|
||||
}
|
||||
// Wait for enable
|
||||
m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
|
||||
m_sinkEnabledCond.wait(lock,
|
||||
[=, this] { return !m_active || IsEnabled(); });
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
@@ -140,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
|
||||
}
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Camera Thread exiting");
|
||||
SDEBUG("Camera Thread exiting");
|
||||
SetConnected(false);
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_locations.empty()) {
|
||||
SERROR("{}", "locations array is empty!?");
|
||||
SERROR("locations array is empty!?");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return nullptr;
|
||||
}
|
||||
@@ -272,7 +273,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
wpi::SmallString<64> contentTypeBuf;
|
||||
wpi::SmallString<64> contentLengthBuf;
|
||||
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
|
||||
SWARNING("{}", "disconnected during headers");
|
||||
SWARNING("disconnected during headers");
|
||||
PutError("disconnected during headers", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -294,7 +295,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -313,7 +314,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("{}", "did not receive a JPEG image");
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
@@ -329,7 +330,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
wpi::HttpRequest req;
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_settingsCond.wait(lock, [=] {
|
||||
m_settingsCond.wait(lock, [=, this] {
|
||||
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
|
||||
});
|
||||
if (!m_active) {
|
||||
@@ -343,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
|
||||
DeviceSendSettings(req);
|
||||
}
|
||||
|
||||
SDEBUG("{}", "Settings Thread exiting");
|
||||
SDEBUG("Settings Thread exiting");
|
||||
}
|
||||
|
||||
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
|
||||
@@ -378,7 +379,7 @@ CS_HttpCameraKind HttpCameraImpl::GetKind() const {
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
bool HttpCameraImpl::SetUrls(wpi::span<const std::string> urls,
|
||||
bool HttpCameraImpl::SetUrls(std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
std::vector<wpi::HttpLocation> locations;
|
||||
for (const auto& url : urls) {
|
||||
@@ -572,14 +573,14 @@ CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
break;
|
||||
}
|
||||
std::string urlStr{url};
|
||||
if (!source->SetUrls(wpi::span{&urlStr, 1}, status)) {
|
||||
if (!source->SetUrls(std::span{&urlStr, 1}, status)) {
|
||||
return 0;
|
||||
}
|
||||
return inst.CreateSource(CS_SOURCE_HTTP, source);
|
||||
}
|
||||
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
if (urls.empty()) {
|
||||
@@ -603,7 +604,7 @@ CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
|
||||
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
|
||||
}
|
||||
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status) {
|
||||
if (urls.empty()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
@@ -18,7 +19,6 @@
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/span.h>
|
||||
#include <wpinet/HttpUtil.h>
|
||||
|
||||
#include "SourceImpl.h"
|
||||
@@ -55,7 +55,7 @@ class HttpCameraImpl : public SourceImpl {
|
||||
void NumSinksEnabledChanged() override;
|
||||
|
||||
CS_HttpCameraKind GetKind() const;
|
||||
bool SetUrls(wpi::span<const std::string> urls, CS_Status* status);
|
||||
bool SetUrls(std::span<const std::string> urls, CS_Status* status);
|
||||
std::vector<std::string> GetUrls() const;
|
||||
|
||||
// Property data
|
||||
|
||||
@@ -86,16 +86,16 @@ class Instance {
|
||||
void DestroySource(CS_Source handle);
|
||||
void DestroySink(CS_Sink handle);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec) {
|
||||
return m_sources.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
return m_sinks.GetAll(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec) {
|
||||
vec.clear();
|
||||
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
|
||||
|
||||
@@ -27,26 +27,37 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
|
||||
#define LOG(level, format, ...) \
|
||||
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
|
||||
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) \
|
||||
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define WARNING(format, ...) \
|
||||
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
|
||||
#define DEBUG0(format, ...) \
|
||||
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG1(format, ...) \
|
||||
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG2(format, ...) \
|
||||
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG3(format, ...) \
|
||||
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define DEBUG4(format, ...) \
|
||||
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), FMT_STRING(format), \
|
||||
__VA_ARGS__)
|
||||
#define SLOG(level, format, ...) \
|
||||
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), \
|
||||
FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#define SERROR(format, ...) SLOG(::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
|
||||
#define SWARNING(format, ...) SLOG(::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
|
||||
#define SINFO(format, ...) SLOG(::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
|
||||
#define SERROR(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SWARNING(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SINFO(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define SDEBUG(format, ...) \
|
||||
@@ -65,11 +76,16 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
|
||||
do { \
|
||||
} while (0)
|
||||
#else
|
||||
#define SDEBUG(format, ...) SLOG(::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) SLOG(::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) SLOG(::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) SLOG(::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) SLOG(::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
|
||||
#define SDEBUG(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG1(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG2(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG3(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define SDEBUG4(format, ...) \
|
||||
SLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif // CSCORE_LOG_H_
|
||||
|
||||
@@ -650,7 +650,7 @@ void MjpegServerImpl::Stop() {
|
||||
// Send HTTP response and a stream of JPG-frames
|
||||
void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
if (m_noStreaming) {
|
||||
SERROR("{}", "Too many simultaneous client streams");
|
||||
SERROR("Too many simultaneous client streams");
|
||||
SendError(os, 503, "Too many simultaneous streams");
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +663,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
SendHeader(oss, 200, "OK", "multipart/x-mixed-replace;boundary=" BOUNDARY);
|
||||
os << oss.str();
|
||||
|
||||
SDEBUG("{}", "Headers send, sending stream now");
|
||||
SDEBUG("Headers send, sending stream now");
|
||||
|
||||
Frame::Time lastFrameTime = 0;
|
||||
Frame::Time timePerFrame = 0;
|
||||
@@ -685,7 +685,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(0.225); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
@@ -783,7 +783,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
wpi::SmallString<128> reqBuf;
|
||||
std::string_view req = is.getline(reqBuf, 4096);
|
||||
if (is.has_error()) {
|
||||
SDEBUG("{}", "error getting request string");
|
||||
SDEBUG("error getting request string");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
} else if (req.find("GET / ") != std::string_view::npos || req == "GET /\n") {
|
||||
kind = kRootPage;
|
||||
} else {
|
||||
SDEBUG("{}", "HTTP request resource not found");
|
||||
SDEBUG("HTTP request resource not found");
|
||||
SendError(os, 404, "Resource not found");
|
||||
return;
|
||||
}
|
||||
@@ -866,11 +866,11 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
SendHeader(os, 200, "OK", "text/plain");
|
||||
os << "Ignored due to no connected source."
|
||||
<< "\r\n";
|
||||
SDEBUG("{}", "Ignored due to no connected source.");
|
||||
SDEBUG("Ignored due to no connected source.");
|
||||
}
|
||||
break;
|
||||
case kGetSettings:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendJSON(os, *source, true);
|
||||
} else {
|
||||
@@ -878,7 +878,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kGetSourceConfig:
|
||||
SDEBUG("{}", "request for JSON file");
|
||||
SDEBUG("request for JSON file");
|
||||
if (auto source = GetSource()) {
|
||||
SendHeader(os, 200, "OK", "application/json");
|
||||
CS_Status status = CS_OK;
|
||||
@@ -889,7 +889,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
}
|
||||
break;
|
||||
case kRootPage:
|
||||
SDEBUG("{}", "request for root page");
|
||||
SDEBUG("request for root page");
|
||||
SendHeader(os, 200, "OK", "text/html");
|
||||
if (auto source = GetSource()) {
|
||||
SendHTML(os, *source, false);
|
||||
@@ -900,7 +900,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
|
||||
break;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving HTTP client thread");
|
||||
SDEBUG("leaving HTTP client thread");
|
||||
}
|
||||
|
||||
// worker thread for clients that connected to this server
|
||||
@@ -927,7 +927,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
return;
|
||||
}
|
||||
|
||||
SDEBUG("{}", "waiting for clients to connect");
|
||||
SDEBUG("waiting for clients to connect");
|
||||
while (m_active) {
|
||||
auto stream = m_acceptor->accept();
|
||||
if (!stream) {
|
||||
@@ -977,7 +977,7 @@ void MjpegServerImpl::ServerThreadMain() {
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
SDEBUG("{}", "leaving server thread");
|
||||
SDEBUG("leaving server thread");
|
||||
}
|
||||
|
||||
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {
|
||||
|
||||
@@ -27,7 +27,7 @@ int PropertyContainer::GetPropertyIndex(std::string_view name) const {
|
||||
return ndx;
|
||||
}
|
||||
|
||||
wpi::span<int> PropertyContainer::EnumerateProperties(
|
||||
std::span<int> PropertyContainer::EnumerateProperties(
|
||||
wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
|
||||
if (!m_properties_cached && !CacheProperties(status)) {
|
||||
return {};
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "PropertyImpl.h"
|
||||
#include "cscore_cpp.h"
|
||||
@@ -33,7 +33,7 @@ class PropertyContainer {
|
||||
virtual ~PropertyContainer() = default;
|
||||
|
||||
int GetPropertyIndex(std::string_view name) const;
|
||||
wpi::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
std::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
|
||||
CS_Status* status) const;
|
||||
CS_PropertyKind GetPropertyKind(int property) const;
|
||||
std::string_view GetPropertyName(int property,
|
||||
|
||||
@@ -127,7 +127,7 @@ void RawSinkImpl::ThreadMain() {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
continue;
|
||||
}
|
||||
SDEBUG4("{}", "waiting for frame");
|
||||
SDEBUG4("waiting for frame");
|
||||
Frame frame = source->GetNextFrame(); // blocks
|
||||
if (!m_active) {
|
||||
break;
|
||||
|
||||
@@ -76,7 +76,7 @@ Frame SourceImpl::GetCurFrame() {
|
||||
Frame SourceImpl::GetNextFrame() {
|
||||
std::unique_lock lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
|
||||
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Frame SourceImpl::GetNextFrame(double timeout) {
|
||||
auto oldTime = m_frame.GetTime();
|
||||
if (!m_frameCv.wait_for(
|
||||
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
|
||||
[=] { return m_frame.GetTime() != oldTime; })) {
|
||||
[=, this] { return m_frame.GetTime() != oldTime; })) {
|
||||
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
|
||||
}
|
||||
return m_frame;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/mutex.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace cs {
|
||||
|
||||
@@ -50,7 +50,7 @@ class UnlimitedHandleResource {
|
||||
std::shared_ptr<TStruct> Free(THandle handle);
|
||||
|
||||
template <typename T>
|
||||
wpi::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
std::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
|
||||
|
||||
std::vector<std::shared_ptr<TStruct>> FreeAll();
|
||||
|
||||
@@ -151,7 +151,7 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
|
||||
|
||||
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
|
||||
template <typename T>
|
||||
inline wpi::span<T>
|
||||
inline std::span<T>
|
||||
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
|
||||
wpi::SmallVectorImpl<T>& vec) {
|
||||
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });
|
||||
|
||||
@@ -286,7 +286,7 @@ CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
return Handle{source, property, Handle::kProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSource(source);
|
||||
@@ -398,7 +398,7 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
return data->source->EnumerateVideoModes(status);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
auto& inst = Instance::GetInstance();
|
||||
@@ -583,7 +583,7 @@ CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
return Handle{sink, property, Handle::kSinkProperty};
|
||||
}
|
||||
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
|
||||
auto data = Instance::GetInstance().GetSink(sink);
|
||||
if (!data) {
|
||||
@@ -865,12 +865,12 @@ void Shutdown() {
|
||||
// Utility Functions
|
||||
//
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSourceHandles(vec);
|
||||
}
|
||||
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status) {
|
||||
return Instance::GetInstance().EnumerateSinkHandles(vec);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <exception>
|
||||
#include <span>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/jni_util.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
#include "cscore_cv.h"
|
||||
@@ -296,7 +296,7 @@ static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
|
||||
}
|
||||
|
||||
static jobjectArray MakeJObject(JNIEnv* env,
|
||||
wpi::span<const cs::RawEvent> arr) {
|
||||
std::span<const cs::RawEvent> arr) {
|
||||
jobjectArray jarr = env->NewObjectArray(arr.size(), videoEventCls, nullptr);
|
||||
if (!jarr) {
|
||||
return nullptr;
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_c.h"
|
||||
|
||||
@@ -203,7 +203,7 @@ CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
|
||||
CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateHttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
CS_HttpCameraKind kind, CS_Status* status);
|
||||
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
|
||||
CS_Status* status);
|
||||
@@ -230,7 +230,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status);
|
||||
bool IsSourceEnabled(CS_Source source, CS_Status* status);
|
||||
CS_Property GetSourceProperty(CS_Source source, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSourceProperties(
|
||||
std::span<CS_Property> EnumerateSourceProperties(
|
||||
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
|
||||
CS_Status* status);
|
||||
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
|
||||
@@ -249,7 +249,7 @@ std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
|
||||
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
|
||||
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
|
||||
wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
CS_Source CopySource(CS_Source source, CS_Status* status);
|
||||
@@ -285,7 +285,7 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
|
||||
* @{
|
||||
*/
|
||||
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status);
|
||||
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
|
||||
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
|
||||
CS_Status* status);
|
||||
std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
@@ -304,7 +304,7 @@ CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
|
||||
int step, int defaultValue, int value,
|
||||
CS_Status* status);
|
||||
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
|
||||
wpi::span<const std::string> choices,
|
||||
std::span<const std::string> choices,
|
||||
CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
@@ -335,7 +335,7 @@ std::string_view GetSinkDescription(CS_Sink sink,
|
||||
CS_Status* status);
|
||||
CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
|
||||
CS_Status* status);
|
||||
wpi::span<CS_Property> EnumerateSinkProperties(
|
||||
std::span<CS_Property> EnumerateSinkProperties(
|
||||
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status);
|
||||
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
|
||||
CS_Property GetSinkSourceProperty(CS_Sink sink, std::string_view name,
|
||||
@@ -430,9 +430,9 @@ void Shutdown();
|
||||
*/
|
||||
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status);
|
||||
|
||||
wpi::span<CS_Source> EnumerateSourceHandles(
|
||||
std::span<CS_Source> EnumerateSourceHandles(
|
||||
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status);
|
||||
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
|
||||
CS_Status* status);
|
||||
|
||||
std::string GetHostname();
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
#define CSCORE_CSCORE_OO_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "cscore_cpp.h"
|
||||
|
||||
namespace cs {
|
||||
@@ -516,7 +515,7 @@ class HttpCamera : public VideoCamera {
|
||||
* @param urls Array of Camera URLs
|
||||
* @param kind Camera kind (e.g. kAxis)
|
||||
*/
|
||||
HttpCamera(std::string_view name, wpi::span<const std::string> urls,
|
||||
HttpCamera(std::string_view name, std::span<const std::string> urls,
|
||||
HttpCameraKind kind = kUnknown);
|
||||
|
||||
/**
|
||||
@@ -541,7 +540,7 @@ class HttpCamera : public VideoCamera {
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
*/
|
||||
void SetUrls(wpi::span<const std::string> urls);
|
||||
void SetUrls(std::span<const std::string> urls);
|
||||
|
||||
/**
|
||||
* Change the URLs used to connect to the camera.
|
||||
@@ -560,7 +559,7 @@ class HttpCamera : public VideoCamera {
|
||||
*/
|
||||
class AxisCamera : public HttpCamera {
|
||||
static std::string HostToUrl(std::string_view host);
|
||||
static std::vector<std::string> HostToUrl(wpi::span<const std::string> hosts);
|
||||
static std::vector<std::string> HostToUrl(std::span<const std::string> hosts);
|
||||
template <typename T>
|
||||
static std::vector<std::string> HostToUrl(std::initializer_list<T> hosts);
|
||||
|
||||
@@ -595,7 +594,7 @@ class AxisCamera : public HttpCamera {
|
||||
* @param name Source name (arbitrary unique identifier)
|
||||
* @param hosts Array of Camera host IPs/DNS names
|
||||
*/
|
||||
AxisCamera(std::string_view name, wpi::span<const std::string> hosts);
|
||||
AxisCamera(std::string_view name, std::span<const std::string> hosts);
|
||||
|
||||
/**
|
||||
* Create a source for an Axis IP camera.
|
||||
@@ -696,7 +695,7 @@ class ImageSource : public VideoSource {
|
||||
* @param choices Choices
|
||||
*/
|
||||
void SetEnumPropertyChoices(const VideoProperty& property,
|
||||
wpi::span<const std::string> choices);
|
||||
std::span<const std::string> choices);
|
||||
|
||||
/**
|
||||
* Configure enum property choices.
|
||||
|
||||
@@ -302,7 +302,7 @@ inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
|
||||
: HttpCamera(name, std::string_view{url}, kind) {}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name,
|
||||
wpi::span<const std::string> urls,
|
||||
std::span<const std::string> urls,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
@@ -329,7 +329,7 @@ inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
|
||||
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
|
||||
}
|
||||
|
||||
inline void HttpCamera::SetUrls(wpi::span<const std::string> urls) {
|
||||
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
|
||||
m_status = 0;
|
||||
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ inline std::vector<std::string> HttpCamera::GetUrls() const {
|
||||
}
|
||||
|
||||
inline std::vector<std::string> AxisCamera::HostToUrl(
|
||||
wpi::span<const std::string> hosts) {
|
||||
std::span<const std::string> hosts) {
|
||||
std::vector<std::string> rv;
|
||||
rv.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
@@ -381,7 +381,7 @@ inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name,
|
||||
wpi::span<const std::string> hosts)
|
||||
std::span<const std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
|
||||
template <typename T>
|
||||
@@ -452,7 +452,7 @@ inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
|
||||
}
|
||||
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, wpi::span<const std::string> choices) {
|
||||
const VideoProperty& property, std::span<const std::string> choices) {
|
||||
m_status = 0;
|
||||
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle notify events
|
||||
if (notify_fd >= 0 && FD_ISSET(notify_fd, &readfds)) {
|
||||
SDEBUG4("{}", "notify event");
|
||||
SDEBUG4("notify event");
|
||||
struct inotify_event event;
|
||||
do {
|
||||
// Read the event structure
|
||||
@@ -483,7 +483,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle commands
|
||||
if (command_fd >= 0 && FD_ISSET(command_fd, &readfds)) {
|
||||
SDEBUG4("{}", "got command");
|
||||
SDEBUG4("got command");
|
||||
// Read it to clear
|
||||
eventfd_t val;
|
||||
eventfd_read(command_fd, &val);
|
||||
@@ -493,7 +493,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Handle frames
|
||||
if (m_streaming && fd >= 0 && FD_ISSET(fd, &readfds)) {
|
||||
SDEBUG4("{}", "grabbing image");
|
||||
SDEBUG4("grabbing image");
|
||||
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf;
|
||||
@@ -501,7 +501,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_DQBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not dequeue buffer");
|
||||
SWARNING("could not dequeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -525,7 +525,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
bool good = true;
|
||||
if (m_mode.pixelFormat == VideoMode::kMJPEG &&
|
||||
!GetJpegSize(image, &width, &height)) {
|
||||
SWARNING("{}", "invalid JPEG image received from camera");
|
||||
SWARNING("invalid JPEG image received from camera");
|
||||
good = false;
|
||||
}
|
||||
if (good) {
|
||||
@@ -536,7 +536,7 @@ void UsbCameraImpl::CameraThreadMain() {
|
||||
|
||||
// Requeue buffer
|
||||
if (DoIoctl(fd, VIDIOC_QBUF, &buf) != 0) {
|
||||
SWARNING("{}", "could not requeue buffer");
|
||||
SWARNING("could not requeue buffer");
|
||||
wasStreaming = m_streaming;
|
||||
DeviceStreamOff();
|
||||
DeviceDisconnect();
|
||||
@@ -579,7 +579,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Try to open the device
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
int fd = open(m_path.c_str(), O_RDWR);
|
||||
if (fd < 0) {
|
||||
return;
|
||||
@@ -587,7 +587,7 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
m_fd = fd;
|
||||
|
||||
// Get capabilities
|
||||
SDEBUG3("{}", "getting capabilities");
|
||||
SDEBUG3("getting capabilities");
|
||||
struct v4l2_capability vcap;
|
||||
std::memset(&vcap, 0, sizeof(vcap));
|
||||
if (DoIoctl(fd, VIDIOC_QUERYCAP, &vcap) >= 0) {
|
||||
@@ -599,18 +599,18 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
|
||||
// Get or restore video mode
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
DeviceSetFPS();
|
||||
|
||||
// Restore settings
|
||||
SDEBUG3("{}", "restoring settings");
|
||||
SDEBUG3("restoring settings");
|
||||
std::unique_lock lock2(m_mutex);
|
||||
for (size_t i = 0; i < m_propertyData.size(); ++i) {
|
||||
const auto prop =
|
||||
@@ -625,21 +625,21 @@ void UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
// Request buffers
|
||||
SDEBUG3("{}", "allocating buffers");
|
||||
SDEBUG3("allocating buffers");
|
||||
struct v4l2_requestbuffers rb;
|
||||
std::memset(&rb, 0, sizeof(rb));
|
||||
rb.count = kNumBuffers;
|
||||
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
rb.memory = V4L2_MEMORY_MMAP;
|
||||
if (DoIoctl(fd, VIDIOC_REQBUFS, &rb) != 0) {
|
||||
SWARNING("{}", "could not allocate buffers");
|
||||
SWARNING("could not allocate buffers");
|
||||
close(fd);
|
||||
m_fd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Map buffers
|
||||
SDEBUG3("{}", "mapping buffers");
|
||||
SDEBUG3("mapping buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -689,7 +689,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
|
||||
// Queue buffers
|
||||
SDEBUG3("{}", "queuing buffers");
|
||||
SDEBUG3("queuing buffers");
|
||||
for (int i = 0; i < kNumBuffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
@@ -708,7 +708,6 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
if (errno == ENOSPC) {
|
||||
// indicates too much USB bandwidth requested
|
||||
SERROR(
|
||||
"{}",
|
||||
"could not start streaming due to USB bandwidth limitations; try a "
|
||||
"lower resolution or a different pixel format (VIDIOC_STREAMON: "
|
||||
"No space left on device)");
|
||||
@@ -718,7 +717,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "enabled streaming");
|
||||
SDEBUG4("enabled streaming");
|
||||
m_streaming = true;
|
||||
return true;
|
||||
}
|
||||
@@ -735,7 +734,7 @@ bool UsbCameraImpl::DeviceStreamOff() {
|
||||
if (DoIoctl(fd, VIDIOC_STREAMOFF, &type) != 0) {
|
||||
return false;
|
||||
}
|
||||
SDEBUG4("{}", "disabled streaming");
|
||||
SDEBUG4("disabled streaming");
|
||||
m_streaming = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1000,7 +999,7 @@ void UsbCameraImpl::DeviceCacheMode() {
|
||||
#endif
|
||||
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (DoIoctl(fd, VIDIOC_G_FMT, &vfmt) != 0) {
|
||||
SERROR("{}", "could not read current video mode");
|
||||
SERROR("could not read current video mode");
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_mode = VideoMode{VideoMode::kMJPEG, 320, 240, 30};
|
||||
return;
|
||||
@@ -1668,7 +1667,7 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
|
||||
::closedir(dp);
|
||||
} else {
|
||||
// *status = ;
|
||||
WPI_ERROR(Instance::GetInstance().logger, "{}", "Could not open /dev");
|
||||
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
SINFO("Connecting to USB camera on {}", m_path);
|
||||
}
|
||||
|
||||
SDEBUG3("{}", "opening device");
|
||||
SDEBUG3("opening device");
|
||||
|
||||
const wchar_t* path = m_widePath.c_str();
|
||||
m_mediaSource = CreateVideoCaptureDevice(path);
|
||||
@@ -520,13 +520,13 @@ bool UsbCameraImpl::DeviceConnect() {
|
||||
}
|
||||
|
||||
if (!m_properties_cached) {
|
||||
SDEBUG3("{}", "caching properties");
|
||||
SDEBUG3("caching properties");
|
||||
DeviceCacheProperties();
|
||||
DeviceCacheVideoModes();
|
||||
DeviceCacheMode();
|
||||
m_properties_cached = true;
|
||||
} else {
|
||||
SDEBUG3("{}", "restoring video mode");
|
||||
SDEBUG3("restoring video mode");
|
||||
DeviceSetMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
class DataLogThread {
|
||||
public:
|
||||
explicit DataLogThread(wpi::log::DataLogReader reader)
|
||||
: m_reader{std::move(reader)}, m_thread{[=] { ReadMain(); }} {}
|
||||
: m_reader{std::move(reader)}, m_thread{[=, this] { ReadMain(); }} {}
|
||||
~DataLogThread();
|
||||
|
||||
bool IsDone() const { return m_done; }
|
||||
|
||||
@@ -107,7 +107,7 @@ static void RebuildEntryTree() {
|
||||
|
||||
// get to leaf
|
||||
auto nodes = &gEntryTree;
|
||||
for (auto part : wpi::drop_back(wpi::span{parts.begin(), parts.end()})) {
|
||||
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
|
||||
auto it =
|
||||
std::find_if(nodes->begin(), nodes->end(),
|
||||
[&](const auto& node) { return node.name == part; });
|
||||
|
||||
@@ -90,7 +90,7 @@ size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::Write(wpi::span<const uint8_t> data) {
|
||||
size_t File::Write(std::span<const uint8_t> data) {
|
||||
auto rv = sftp_write(m_handle, data.data(), data.size());
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace sftp {
|
||||
|
||||
struct Attributes {
|
||||
@@ -53,7 +52,7 @@ class File {
|
||||
size_t Read(void* buf, uint32_t count);
|
||||
AsyncId AsyncReadBegin(uint32_t len) const;
|
||||
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
|
||||
size_t Write(wpi::span<const uint8_t> data);
|
||||
size_t Write(std::span<const uint8_t> data);
|
||||
|
||||
void Seek(uint64_t offset);
|
||||
uint64_t Tell() const;
|
||||
|
||||
@@ -57,7 +57,6 @@ doxygen {
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
exclude 'wpi/ghc/filesystem.hpp'
|
||||
exclude 'wpi/span.h'
|
||||
|
||||
// Drake
|
||||
exclude 'drake/common/**'
|
||||
@@ -198,6 +197,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
dependsOn project(':wpilibj').generateJavaVersion
|
||||
dependsOn project(':hal').generateUsageReporting
|
||||
dependsOn project(':wpimath').generateNat
|
||||
dependsOn project(':ntcore').ntcoreGenerateJavaTypes
|
||||
source project(':hal').sourceSets.main.java
|
||||
source project(':wpiutil').sourceSets.main.java
|
||||
source project(':cscore').sourceSets.main.java
|
||||
|
||||
@@ -29,7 +29,7 @@ add_library(fieldImages ${field_images_resources_src})
|
||||
set_target_properties(fieldImages PROPERTIES DEBUG_POSTFIX "d")
|
||||
|
||||
set_property(TARGET fieldImages PROPERTY FOLDER "libraries")
|
||||
target_compile_features(fieldImages PUBLIC cxx_std_17)
|
||||
target_compile_features(fieldImages PUBLIC cxx_std_20)
|
||||
if (MSVC)
|
||||
target_compile_options(fieldImages PUBLIC /bigobj)
|
||||
endif()
|
||||
|
||||
@@ -24,6 +24,7 @@ includeOtherLibs {
|
||||
^fmt/
|
||||
^frc/
|
||||
^imgui
|
||||
^networktables/
|
||||
^ntcore
|
||||
^wpi/
|
||||
^wpigui
|
||||
|
||||
@@ -137,7 +137,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
return
|
||||
}
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
|
||||
project(':ntcore').addNtcoreDependency(it, 'shared')
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
@@ -176,7 +176,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
|
||||
lib project: ':cscore', library: 'cscore', linkage: 'static'
|
||||
lib library: 'glassnt', linkage: 'static'
|
||||
lib library: nativeName, linkage: 'static'
|
||||
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
|
||||
project(':ntcore').addNtcoreDependency(it, 'static')
|
||||
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
|
||||
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
|
||||
|
||||
@@ -42,6 +42,7 @@ static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
|
||||
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
|
||||
static glass::LogData gNetworkTablesLog;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesInfoWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
|
||||
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
|
||||
|
||||
@@ -78,8 +79,7 @@ static void NtInitialize() {
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
bool timedOut;
|
||||
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
|
||||
for (auto&& event : nt::ReadConnectionListenerQueue(poller)) {
|
||||
if (event.connected) {
|
||||
glfwSetWindowTitle(
|
||||
win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
|
||||
@@ -94,8 +94,7 @@ static void NtInitialize() {
|
||||
auto logPoller = nt::CreateLoggerPoller(inst);
|
||||
nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100);
|
||||
gui::AddEarlyExecute([logPoller] {
|
||||
bool timedOut;
|
||||
for (auto&& msg : nt::PollLogger(logPoller, 0, &timedOut)) {
|
||||
for (auto&& msg : nt::ReadLoggerQueue(logPoller)) {
|
||||
const char* level = "";
|
||||
if (msg.level >= NT_LOG_CRITICAL) {
|
||||
level = "CRITICAL: ";
|
||||
@@ -132,9 +131,21 @@ static void NtInitialize() {
|
||||
gNetworkTablesWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
|
||||
|
||||
// NetworkTables info window
|
||||
gNetworkTablesInfoWindow = std::make_unique<glass::Window>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Info"),
|
||||
"NetworkTables Info");
|
||||
gNetworkTablesInfoWindow->SetView(glass::MakeFunctionView(
|
||||
[&] { glass::DisplayNetworkTablesInfo(gNetworkTablesModel.get()); }));
|
||||
gNetworkTablesInfoWindow->SetDefaultPos(250, 130);
|
||||
gNetworkTablesInfoWindow->SetDefaultSize(750, 145);
|
||||
gNetworkTablesInfoWindow->SetDefaultVisibility(glass::Window::kHide);
|
||||
gNetworkTablesInfoWindow->DisableRenamePopup();
|
||||
gui::AddLateExecute([] { gNetworkTablesInfoWindow->Display(); });
|
||||
|
||||
// NetworkTables settings window
|
||||
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
|
||||
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
|
||||
"glass", glass::GetStorageRoot().GetChild("NetworkTables Settings"));
|
||||
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
|
||||
|
||||
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
|
||||
@@ -218,6 +229,9 @@ int main(int argc, char** argv) {
|
||||
if (gNetworkTablesWindow) {
|
||||
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
|
||||
}
|
||||
if (gNetworkTablesInfoWindow) {
|
||||
gNetworkTablesInfoWindow->DisplayMenuItem("NetworkTables Info");
|
||||
}
|
||||
if (gNetworkTablesLogWindow) {
|
||||
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
} \
|
||||
\
|
||||
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
|
||||
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
|
||||
std::string_view key, std::span<const ArrCType> defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/numbers>
|
||||
#include <numbers>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
@@ -54,7 +54,7 @@ void glass::DisplayGyro(GyroModel* m) {
|
||||
|
||||
// Draw the spokes at every 5 degrees and a "major" spoke every 45 degrees.
|
||||
for (int i = -175; i <= 180; i += 5) {
|
||||
double radians = i * 2 * wpi::numbers::pi / 360.0;
|
||||
double radians = i * 2 * std::numbers::pi / 360.0;
|
||||
ImVec2 direction(std::sin(radians), -std::cos(radians));
|
||||
|
||||
bool major = i % 45 == 0;
|
||||
@@ -74,7 +74,7 @@ void glass::DisplayGyro(GyroModel* m) {
|
||||
|
||||
draw->AddCircleFilled(center, radius * 0.075, secondaryColor, 50);
|
||||
|
||||
double radians = value * 2 * wpi::numbers::pi / 360.0;
|
||||
double radians = value * 2 * std::numbers::pi / 360.0;
|
||||
draw->AddLine(
|
||||
center - ImVec2(1, 0),
|
||||
center + ImVec2(std::sin(radians), -std::cos(radians)) * radius * 0.95f,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/numbers>
|
||||
#include <numbers>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
@@ -55,11 +55,11 @@ void glass::DisplayDrive(DriveModel* m) {
|
||||
draw->AddTriangleFilled(
|
||||
arrowPos,
|
||||
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
|
||||
std::cos(angle + wpi::numbers::pi / 4),
|
||||
std::sin(angle + wpi::numbers::pi / 4)),
|
||||
std::cos(angle + std::numbers::pi / 4),
|
||||
std::sin(angle + std::numbers::pi / 4)),
|
||||
arrowPos + ImRotate(ImVec2(0.0f, 7.5f),
|
||||
std::cos(angle - wpi::numbers::pi / 4),
|
||||
std::sin(angle - wpi::numbers::pi / 4)),
|
||||
std::cos(angle - std::numbers::pi / 4),
|
||||
std::sin(angle - std::numbers::pi / 4)),
|
||||
color);
|
||||
};
|
||||
|
||||
@@ -88,30 +88,30 @@ void glass::DisplayDrive(DriveModel* m) {
|
||||
if (rotation != 0) {
|
||||
float radius = 60.0f;
|
||||
double a1 = 0.0;
|
||||
double a2 = wpi::numbers::pi / 2 * rotation;
|
||||
double a2 = std::numbers::pi / 2 * rotation;
|
||||
|
||||
// PathArcTo requires a_min <= a_max, and rotation can be negative
|
||||
if (a1 > a2) {
|
||||
draw->PathArcTo(center, radius, a2, a1, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a2 + wpi::numbers::pi,
|
||||
a1 + wpi::numbers::pi, 20);
|
||||
draw->PathArcTo(center, radius, a2 + std::numbers::pi,
|
||||
a1 + std::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
} else {
|
||||
draw->PathArcTo(center, radius, a1, a2, 20);
|
||||
draw->PathStroke(color, false);
|
||||
draw->PathArcTo(center, radius, a1 + wpi::numbers::pi,
|
||||
a2 + wpi::numbers::pi, 20);
|
||||
draw->PathArcTo(center, radius, a1 + std::numbers::pi,
|
||||
a2 + std::numbers::pi, 20);
|
||||
draw->PathStroke(color, false);
|
||||
}
|
||||
|
||||
double adder = rotation < 0 ? wpi::numbers::pi : 0;
|
||||
double adder = rotation < 0 ? std::numbers::pi : 0;
|
||||
|
||||
auto arrowPos =
|
||||
center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
|
||||
drawArrow(arrowPos, a2 + adder);
|
||||
|
||||
a2 += wpi::numbers::pi;
|
||||
a2 += std::numbers::pi;
|
||||
arrowPos = center + ImVec2(radius * -std::cos(a2), radius * -std::sin(a2));
|
||||
drawArrow(arrowPos, a2 + adder);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class PopupState {
|
||||
|
||||
SelectedTargetInfo* GetTarget() { return &m_target; }
|
||||
FieldObjectModel* GetInsertModel() { return m_insertModel; }
|
||||
wpi::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
|
||||
std::span<const frc::Pose2d> GetInsertPoses() const { return m_insertPoses; }
|
||||
|
||||
void Display(Field2DModel* model, const FieldFrameData& ffd);
|
||||
|
||||
@@ -189,7 +189,7 @@ class ObjectInfo {
|
||||
|
||||
DisplayOptions GetDisplayOptions() const;
|
||||
void DisplaySettings();
|
||||
void DrawLine(ImDrawList* drawList, wpi::span<const ImVec2> points) const;
|
||||
void DrawLine(ImDrawList* drawList, std::span<const ImVec2> points) const;
|
||||
|
||||
void LoadImage();
|
||||
const gui::Texture& GetTexture() const { return m_texture; }
|
||||
@@ -617,7 +617,7 @@ void ObjectInfo::DisplaySettings() {
|
||||
}
|
||||
|
||||
void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
wpi::span<const ImVec2> points) const {
|
||||
std::span<const ImVec2> points) const {
|
||||
if (points.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class PlotSeries {
|
||||
return m_digital.GetValue() == kDigital ||
|
||||
(m_digital.GetValue() == kAuto && m_source && m_source->IsDigital());
|
||||
}
|
||||
void AppendValue(double value, uint64_t time);
|
||||
void AppendValue(double value, int64_t time);
|
||||
|
||||
// source linkage
|
||||
DataSource* m_source = nullptr;
|
||||
@@ -248,10 +248,10 @@ void PlotSeries::SetSource(DataSource* source) {
|
||||
AppendValue(source->GetValue(), 0);
|
||||
|
||||
m_newValueConn = source->valueChanged.connect_connection(
|
||||
[this](double value, uint64_t time) { AppendValue(value, time); });
|
||||
[this](double value, int64_t time) { AppendValue(value, time); });
|
||||
}
|
||||
|
||||
void PlotSeries::AppendValue(double value, uint64_t timeUs) {
|
||||
void PlotSeries::AppendValue(double value, int64_t timeUs) {
|
||||
double time = (timeUs != 0 ? timeUs : wpi::Now()) * 1.0e-6;
|
||||
if (IsDigital()) {
|
||||
if (m_size < kMaxSize) {
|
||||
|
||||
@@ -10,16 +10,13 @@ using namespace glass;
|
||||
|
||||
EnumSetting::EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices)
|
||||
: m_str{str}, m_choices{choices}, m_value{defaultValue} {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : choices) {
|
||||
if (str == choice) {
|
||||
m_value = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
: m_str{str}, m_choices{choices}, m_defaultValue{defaultValue} {}
|
||||
|
||||
int EnumSetting::GetValue() const {
|
||||
if (m_value == -1) {
|
||||
UpdateValue();
|
||||
}
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void EnumSetting::SetValue(int value) {
|
||||
@@ -29,6 +26,9 @@ void EnumSetting::SetValue(int value) {
|
||||
|
||||
bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
int popup_max_height_in_items) {
|
||||
if (m_value == -1) {
|
||||
UpdateValue();
|
||||
}
|
||||
if (ImGui::Combo(
|
||||
label, &m_value, m_choices.data(),
|
||||
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
|
||||
@@ -38,3 +38,17 @@ bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnumSetting::UpdateValue() const {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : m_choices) {
|
||||
if (m_str == choice) {
|
||||
m_value = i;
|
||||
return;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// no match, default it
|
||||
m_value = m_defaultValue;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/spinlock.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -38,11 +36,13 @@ class DataSource {
|
||||
void SetDigital(bool digital) { m_digital = digital; }
|
||||
bool IsDigital() const { return m_digital; }
|
||||
|
||||
void SetValue(double value, uint64_t time = 0) {
|
||||
void SetValue(double value, int64_t time = 0) {
|
||||
m_value = value;
|
||||
m_valueTime = time;
|
||||
valueChanged(value, time);
|
||||
}
|
||||
double GetValue() const { return m_value; }
|
||||
int64_t GetValueTime() const { return m_valueTime; }
|
||||
|
||||
// drag source helpers
|
||||
void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3);
|
||||
@@ -59,7 +59,7 @@ class DataSource {
|
||||
ImGuiInputTextFlags flags = 0) const;
|
||||
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
|
||||
|
||||
wpi::sig::SignalBase<wpi::spinlock, double, uint64_t> valueChanged;
|
||||
wpi::sig::Signal<double, int64_t> valueChanged;
|
||||
|
||||
static DataSource* Find(std::string_view id);
|
||||
|
||||
@@ -69,7 +69,8 @@ class DataSource {
|
||||
std::string m_id;
|
||||
std::string& m_name;
|
||||
bool m_digital = false;
|
||||
std::atomic<double> m_value = 0;
|
||||
double m_value = 0;
|
||||
int64_t m_valueTime = 0;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
@@ -15,7 +16,6 @@
|
||||
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/iterator_range.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
namespace wpi {
|
||||
class json;
|
||||
@@ -137,17 +137,17 @@ class Storage {
|
||||
std::string_view defaultVal = {});
|
||||
|
||||
std::vector<int>& GetIntArray(std::string_view key,
|
||||
wpi::span<const int> defaultVal = {});
|
||||
std::span<const int> defaultVal = {});
|
||||
std::vector<int64_t>& GetInt64Array(std::string_view key,
|
||||
wpi::span<const int64_t> defaultVal = {});
|
||||
std::span<const int64_t> defaultVal = {});
|
||||
std::vector<int>& GetBoolArray(std::string_view key,
|
||||
wpi::span<const int> defaultVal = {});
|
||||
std::span<const int> defaultVal = {});
|
||||
std::vector<float>& GetFloatArray(std::string_view key,
|
||||
wpi::span<const float> defaultVal = {});
|
||||
std::span<const float> defaultVal = {});
|
||||
std::vector<double>& GetDoubleArray(std::string_view key,
|
||||
wpi::span<const double> defaultVal = {});
|
||||
std::span<const double> defaultVal = {});
|
||||
std::vector<std::string>& GetStringArray(
|
||||
std::string_view key, wpi::span<const std::string> defaultVal = {});
|
||||
std::string_view key, std::span<const std::string> defaultVal = {});
|
||||
std::vector<std::unique_ptr<Storage>>& GetChildArray(std::string_view key);
|
||||
|
||||
Value* FindValue(std::string_view key);
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <wpi/function_ref.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
|
||||
@@ -27,7 +28,7 @@ class LEDDisplayModel : public glass::Model {
|
||||
|
||||
virtual bool IsRunning() = 0;
|
||||
|
||||
virtual wpi::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
|
||||
virtual std::span<const Data> GetData(wpi::SmallVectorImpl<Data>& buf) = 0;
|
||||
};
|
||||
|
||||
class LEDDisplaysModel : public glass::Model {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
#include <frc/geometry/Pose2d.h>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <frc/geometry/Translation2d.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/function_ref.h>
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
@@ -22,8 +22,8 @@ class FieldObjectModel : public Model {
|
||||
public:
|
||||
virtual const char* GetName() const = 0;
|
||||
|
||||
virtual wpi::span<const frc::Pose2d> GetPoses() = 0;
|
||||
virtual void SetPoses(wpi::span<const frc::Pose2d> poses) = 0;
|
||||
virtual std::span<const frc::Pose2d> GetPoses() = 0;
|
||||
virtual void SetPoses(std::span<const 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;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/span.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -21,10 +19,7 @@ class StringChooserModel : public Model {
|
||||
virtual const std::string& GetActive() = 0;
|
||||
virtual const std::vector<std::string>& GetOptions() = 0;
|
||||
|
||||
virtual void SetDefault(std::string_view val) = 0;
|
||||
virtual void SetSelected(std::string_view val) = 0;
|
||||
virtual void SetActive(std::string_view val) = 0;
|
||||
virtual void SetOptions(wpi::span<const std::string> val) = 0;
|
||||
};
|
||||
|
||||
void DisplayStringChooser(StringChooserModel* model);
|
||||
|
||||
@@ -15,7 +15,7 @@ class EnumSetting {
|
||||
EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices);
|
||||
|
||||
int GetValue() const { return m_value; }
|
||||
int GetValue() const;
|
||||
void SetValue(int value);
|
||||
|
||||
// updates internal value, returns true on change
|
||||
@@ -23,9 +23,12 @@ class EnumSetting {
|
||||
int popup_max_height_in_items = -1);
|
||||
|
||||
private:
|
||||
void UpdateValue() const;
|
||||
|
||||
std::string& m_str;
|
||||
wpi::SmallVector<const char*, 8> m_choices;
|
||||
int m_value;
|
||||
int m_defaultValue;
|
||||
mutable int m_value = -1;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -10,49 +10,38 @@
|
||||
using namespace glass;
|
||||
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(std::string_view path)
|
||||
: NTCommandSchedulerModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTCommandSchedulerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(NT_Inst instance,
|
||||
NTCommandSchedulerModel::NTCommandSchedulerModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_commands(m_nt.GetEntry(fmt::format("{}/Names", path))),
|
||||
m_ids(m_nt.GetEntry(fmt::format("{}/Ids", path))),
|
||||
m_cancel(m_nt.GetEntry(fmt::format("{}/Cancel", path))),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_commands);
|
||||
m_nt.AddListener(m_ids);
|
||||
m_nt.AddListener(m_cancel);
|
||||
}
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_commands{inst.GetStringArrayTopic(fmt::format("{}/Names", path))
|
||||
.Subscribe({})},
|
||||
m_ids{
|
||||
inst.GetIntegerArrayTopic(fmt::format("{}/Ids", path)).Subscribe({})},
|
||||
m_cancel{
|
||||
inst.GetIntegerArrayTopic(fmt::format("{}/Cancel", path)).Publish()},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTCommandSchedulerModel::CancelCommand(size_t index) {
|
||||
if (index < m_idsValue.size()) {
|
||||
nt::SetEntryValue(
|
||||
m_cancel, nt::NetworkTableValue::MakeDoubleArray({m_idsValue[index]}));
|
||||
m_cancel.Set({{m_idsValue[index]}});
|
||||
}
|
||||
}
|
||||
|
||||
void NTCommandSchedulerModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_commands) {
|
||||
if (event.value && event.value->IsStringArray()) {
|
||||
auto arr = event.value->GetStringArray();
|
||||
m_commandsValue.assign(arr.begin(), arr.end());
|
||||
}
|
||||
} else if (event.entry == m_ids) {
|
||||
if (event.value && event.value->IsDoubleArray()) {
|
||||
auto arr = event.value->GetDoubleArray();
|
||||
m_idsValue.assign(arr.begin(), arr.end());
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_commands.ReadQueue()) {
|
||||
m_commandsValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_ids.ReadQueue()) {
|
||||
m_idsValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTCommandSchedulerModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_commands) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_commands.Exists();
|
||||
}
|
||||
|
||||
@@ -10,38 +10,32 @@
|
||||
using namespace glass;
|
||||
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(std::string_view path)
|
||||
: NTCommandSelectorModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTCommandSelectorModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(NT_Inst instance,
|
||||
NTCommandSelectorModel::NTCommandSelectorModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_running(m_nt.GetEntry(fmt::format("{}/running", path))),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_runningData(fmt::format("NTCmd:{}", path)),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
: m_inst{inst},
|
||||
m_running{inst.GetBooleanTopic(fmt::format("{}/running", path))
|
||||
.GetEntry(false)},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_runningData{fmt::format("NTCmd:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {
|
||||
m_runningData.SetDigital(true);
|
||||
m_nt.AddListener(m_running);
|
||||
m_nt.AddListener(m_name);
|
||||
}
|
||||
|
||||
void NTCommandSelectorModel::SetRunning(bool run) {
|
||||
nt::SetEntryValue(m_running, nt::NetworkTableValue::MakeBoolean(run));
|
||||
m_running.Set(run);
|
||||
}
|
||||
|
||||
void NTCommandSelectorModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_running) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_runningData.SetValue(event.value->GetBoolean());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_running.ReadQueue()) {
|
||||
m_runningData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTCommandSelectorModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_running) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_running.Exists();
|
||||
}
|
||||
|
||||
@@ -12,46 +12,40 @@
|
||||
using namespace glass;
|
||||
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(std::string_view path)
|
||||
: NTDifferentialDriveModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTDifferentialDriveModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(NT_Inst instance,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
|
||||
m_lPercent(m_nt.GetEntry(fmt::format("{}/Left Motor Speed", path))),
|
||||
m_rPercent(m_nt.GetEntry(fmt::format("{}/Right Motor Speed", path))),
|
||||
m_nameValue(wpi::rsplit(path, '/').second),
|
||||
m_lPercentData(fmt::format("NTDiffDriveL:{}", path)),
|
||||
m_rPercentData(fmt::format("NTDiffDriveR:{}", path)) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
m_nt.AddListener(m_lPercent);
|
||||
m_nt.AddListener(m_rPercent);
|
||||
NTDifferentialDriveModel::NTDifferentialDriveModel(
|
||||
nt::NetworkTableInstance inst, std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(false)},
|
||||
m_lPercent{inst.GetDoubleTopic(fmt::format("{}/Left Motor Speed", path))
|
||||
.GetEntry(0)},
|
||||
m_rPercent{inst.GetDoubleTopic(fmt::format("{}/Right Motor Speed", path))
|
||||
.GetEntry(0)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second},
|
||||
m_lPercentData{fmt::format("NTDiffDriveL:{}", path)},
|
||||
m_rPercentData{fmt::format("NTDiffDriveR:{}", path)} {
|
||||
m_wheels.emplace_back("L % Output", &m_lPercentData,
|
||||
[this](auto value) { m_lPercent.Set(value); });
|
||||
|
||||
m_wheels.emplace_back("L % Output", &m_lPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_lPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
|
||||
m_wheels.emplace_back("R % Output", &m_rPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_rPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
m_wheels.emplace_back("R % Output", &m_rPercentData,
|
||||
[this](auto value) { m_rPercent.Set(value); });
|
||||
}
|
||||
|
||||
void NTDifferentialDriveModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name && event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
} else if (event.entry == m_lPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_lPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_rPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_rPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_controllable && event.value &&
|
||||
event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_lPercent.ReadQueue()) {
|
||||
m_lPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_rPercent.ReadQueue()) {
|
||||
m_rPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
|
||||
double l = m_lPercentData.GetValue();
|
||||
@@ -62,5 +56,5 @@ void NTDifferentialDriveModel::Update() {
|
||||
}
|
||||
|
||||
bool NTDifferentialDriveModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_lPercent) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_lPercent.Exists();
|
||||
}
|
||||
|
||||
@@ -10,34 +10,28 @@
|
||||
using namespace glass;
|
||||
|
||||
NTDigitalInputModel::NTDigitalInputModel(std::string_view path)
|
||||
: NTDigitalInputModel{nt::GetDefaultInstance(), path} {}
|
||||
: NTDigitalInputModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTDigitalInputModel::NTDigitalInputModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
|
||||
m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
|
||||
NTDigitalInputModel::NTDigitalInputModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_value{inst.GetBooleanTopic(fmt::format("{}/Value", path))
|
||||
.Subscribe(false, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_valueData{fmt::format("NT_DIn:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {
|
||||
m_nt.AddListener(m_value);
|
||||
m_nt.AddListener(m_name);
|
||||
|
||||
m_valueData.SetDigital(true);
|
||||
}
|
||||
|
||||
void NTDigitalInputModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_value) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_valueData.SetValue(event.value->GetBoolean());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_value.ReadQueue()) {
|
||||
m_valueData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTDigitalInputModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
}
|
||||
|
||||
@@ -9,43 +9,36 @@
|
||||
using namespace glass;
|
||||
|
||||
NTDigitalOutputModel::NTDigitalOutputModel(std::string_view path)
|
||||
: NTDigitalOutputModel{nt::GetDefaultInstance(), path} {}
|
||||
: NTDigitalOutputModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTDigitalOutputModel::NTDigitalOutputModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
m_value{m_nt.GetEntry(fmt::format("{}/Value", path))},
|
||||
m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
|
||||
m_controllable{m_nt.GetEntry(fmt::format("{}/.controllable", path))},
|
||||
NTDigitalOutputModel::NTDigitalOutputModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_value{inst.GetBooleanTopic(fmt::format("{}/Value", path))
|
||||
.GetEntry(false, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(false)},
|
||||
m_valueData{fmt::format("NT_DOut:{}", path)} {
|
||||
m_nt.AddListener(m_value);
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
|
||||
m_valueData.SetDigital(true);
|
||||
}
|
||||
|
||||
void NTDigitalOutputModel::SetValue(bool val) {
|
||||
nt::SetEntryValue(m_value, nt::Value::MakeBoolean(val));
|
||||
m_value.Set(val);
|
||||
}
|
||||
|
||||
void NTDigitalOutputModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_value) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_valueData.SetValue(event.value->GetBoolean());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_controllable) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_value.ReadQueue()) {
|
||||
m_valueData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NTDigitalOutputModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
}
|
||||
|
||||
@@ -13,15 +13,19 @@
|
||||
using namespace glass;
|
||||
|
||||
NTFMSModel::NTFMSModel(std::string_view path)
|
||||
: NTFMSModel{nt::GetDefaultInstance(), path} {}
|
||||
: NTFMSModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTFMSModel::NTFMSModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
NTFMSModel::NTFMSModel(nt::NetworkTableInstance inst, std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_gameSpecificMessage{
|
||||
m_nt.GetEntry(fmt::format("{}/GameSpecificMessage", path))},
|
||||
m_alliance{m_nt.GetEntry(fmt::format("{}/IsRedAlliance", path))},
|
||||
m_station{m_nt.GetEntry(fmt::format("{}/StationNumber", path))},
|
||||
m_controlWord{m_nt.GetEntry(fmt::format("{}/FMSControlData", path))},
|
||||
inst.GetStringTopic(fmt::format("{}/GameSpecificMessage", path))
|
||||
.Subscribe("")},
|
||||
m_alliance{inst.GetBooleanTopic(fmt::format("{}/IsRedAlliance", path))
|
||||
.Subscribe(false, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_station{inst.GetIntegerTopic(fmt::format("{}/StationNumber", path))
|
||||
.Subscribe(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_controlWord{inst.GetIntegerTopic(fmt::format("{}/FMSControlData", path))
|
||||
.Subscribe(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_fmsAttached{fmt::format("NT_FMS:FMSAttached:{}", path)},
|
||||
m_dsAttached{fmt::format("NT_FMS:DSAttached:{}", path)},
|
||||
m_allianceStationId{fmt::format("NT_FMS:AllianceStationID:{}", path)},
|
||||
@@ -29,10 +33,6 @@ NTFMSModel::NTFMSModel(NT_Inst inst, std::string_view path)
|
||||
m_enabled{fmt::format("NT_FMS:RobotEnabled:{}", path)},
|
||||
m_test{fmt::format("NT_FMS:TestMode:{}", path)},
|
||||
m_autonomous{fmt::format("NT_FMS:AutonomousMode:{}", path)} {
|
||||
m_nt.AddListener(m_alliance);
|
||||
m_nt.AddListener(m_station);
|
||||
m_nt.AddListener(m_controlWord);
|
||||
|
||||
m_fmsAttached.SetDigital(true);
|
||||
m_dsAttached.SetDigital(true);
|
||||
m_estop.SetDigital(true);
|
||||
@@ -43,49 +43,35 @@ NTFMSModel::NTFMSModel(NT_Inst inst, std::string_view path)
|
||||
|
||||
std::string_view NTFMSModel::GetGameSpecificMessage(
|
||||
wpi::SmallVectorImpl<char>& buf) {
|
||||
buf.clear();
|
||||
auto value = nt::GetEntryValue(m_gameSpecificMessage);
|
||||
if (value && value->IsString()) {
|
||||
auto str = value->GetString();
|
||||
buf.append(str.begin(), str.end());
|
||||
}
|
||||
return std::string_view{buf.data(), buf.size()};
|
||||
return m_gameSpecificMessage.Get(buf, "");
|
||||
}
|
||||
|
||||
void NTFMSModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_alliance) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
int allianceStationId = m_allianceStationId.GetValue();
|
||||
allianceStationId %= 3;
|
||||
// true if red
|
||||
allianceStationId += 3 * (event.value->GetBoolean() ? 0 : 1);
|
||||
m_allianceStationId.SetValue(allianceStationId);
|
||||
}
|
||||
} else if (event.entry == m_station) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
int allianceStationId = m_allianceStationId.GetValue();
|
||||
bool isRed = (allianceStationId < 3);
|
||||
// the NT value is 1-indexed
|
||||
m_allianceStationId.SetValue(event.value->GetDouble() - 1 +
|
||||
3 * (isRed ? 0 : 1));
|
||||
}
|
||||
} else if (event.entry == m_controlWord) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
uint32_t controlWord = event.value->GetDouble();
|
||||
// See HAL_ControlWord definition
|
||||
auto time = wpi::Now();
|
||||
m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, time);
|
||||
m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, time);
|
||||
m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, time);
|
||||
m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, time);
|
||||
m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, time);
|
||||
m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, time);
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_alliance.ReadQueue()) {
|
||||
int allianceStationId = m_allianceStationId.GetValue();
|
||||
allianceStationId %= 3;
|
||||
// true if red
|
||||
allianceStationId += 3 * (v.value ? 0 : 1);
|
||||
m_allianceStationId.SetValue(allianceStationId, v.time);
|
||||
}
|
||||
for (auto&& v : m_station.ReadQueue()) {
|
||||
int allianceStationId = m_allianceStationId.GetValue();
|
||||
bool isRed = (allianceStationId < 3);
|
||||
// the NT value is 1-indexed
|
||||
m_allianceStationId.SetValue(v.value - 1 + 3 * (isRed ? 0 : 1), v.time);
|
||||
}
|
||||
for (auto&& v : m_controlWord.ReadQueue()) {
|
||||
uint32_t controlWord = v.value;
|
||||
// See HAL_ControlWord definition
|
||||
m_enabled.SetValue(((controlWord & 0x01) != 0) ? 1 : 0, v.time);
|
||||
m_autonomous.SetValue(((controlWord & 0x02) != 0) ? 1 : 0, v.time);
|
||||
m_test.SetValue(((controlWord & 0x04) != 0) ? 1 : 0, v.time);
|
||||
m_estop.SetValue(((controlWord & 0x08) != 0) ? 1 : 0, v.time);
|
||||
m_fmsAttached.SetValue(((controlWord & 0x10) != 0) ? 1 : 0, v.time);
|
||||
m_dsAttached.SetValue(((controlWord & 0x20) != 0) ? 1 : 0, v.time);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTFMSModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_controlWord) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_controlWord.Exists();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <networktables/DoubleArrayTopic.h>
|
||||
#include <networktables/MultiSubscriber.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/Endian.h>
|
||||
#include <wpi/MathExtras.h>
|
||||
@@ -18,24 +20,20 @@ using namespace glass;
|
||||
|
||||
class NTField2DModel::ObjectModel : public FieldObjectModel {
|
||||
public:
|
||||
ObjectModel(std::string_view name, NT_Entry entry)
|
||||
: m_name{name}, m_entry{entry} {}
|
||||
ObjectModel(std::string_view name, nt::DoubleArrayTopic topic)
|
||||
: m_name{name}, m_topic{topic} {}
|
||||
|
||||
const char* GetName() const override { return m_name.c_str(); }
|
||||
NT_Entry GetEntry() const { return m_entry; }
|
||||
nt::DoubleArrayTopic GetTopic() const { return m_topic; }
|
||||
|
||||
void NTUpdate(const nt::Value& value);
|
||||
|
||||
void Update() override {
|
||||
if (auto value = nt::GetEntryValue(m_entry)) {
|
||||
NTUpdate(*value);
|
||||
}
|
||||
}
|
||||
bool Exists() override { return nt::GetEntryType(m_entry) != NT_UNASSIGNED; }
|
||||
void Update() override {}
|
||||
bool Exists() override { return m_topic.Exists(); }
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
wpi::span<const frc::Pose2d> GetPoses() override { return m_poses; }
|
||||
void SetPoses(wpi::span<const frc::Pose2d> poses) override;
|
||||
std::span<const frc::Pose2d> GetPoses() override { return m_poses; }
|
||||
void SetPoses(std::span<const 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;
|
||||
@@ -44,7 +42,8 @@ class NTField2DModel::ObjectModel : public FieldObjectModel {
|
||||
void UpdateNT();
|
||||
|
||||
std::string m_name;
|
||||
NT_Entry m_entry;
|
||||
nt::DoubleArrayTopic m_topic;
|
||||
nt::DoubleArrayPublisher m_pub;
|
||||
|
||||
std::vector<frc::Pose2d> m_poses;
|
||||
};
|
||||
@@ -62,66 +61,24 @@ void NTField2DModel::ObjectModel::NTUpdate(const nt::Value& value) {
|
||||
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
|
||||
std::string_view data = value.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.data();
|
||||
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}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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().value());
|
||||
arr.push_back(translation.Y().value());
|
||||
arr.push_back(pose.Rotation().Degrees().value());
|
||||
}
|
||||
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().value()));
|
||||
p += 8;
|
||||
wpi::support::endian::write64be(
|
||||
p, wpi::DoubleToBits(translation.Y().value()));
|
||||
p += 8;
|
||||
wpi::support::endian::write64be(
|
||||
p, wpi::DoubleToBits(pose.Rotation().Degrees().value()));
|
||||
p += 8;
|
||||
}
|
||||
nt::SetEntryTypeValue(m_entry,
|
||||
nt::Value::MakeRaw({arr.data(), arr.size()}));
|
||||
wpi::SmallVector<double, 9> arr;
|
||||
for (auto&& pose : m_poses) {
|
||||
auto& translation = pose.Translation();
|
||||
arr.push_back(translation.X().value());
|
||||
arr.push_back(translation.Y().value());
|
||||
arr.push_back(pose.Rotation().Degrees().value());
|
||||
}
|
||||
if (!m_pub) {
|
||||
m_pub = m_topic.Publish();
|
||||
}
|
||||
m_pub.Set(arr);
|
||||
}
|
||||
|
||||
void NTField2DModel::ObjectModel::SetPoses(wpi::span<const frc::Pose2d> poses) {
|
||||
void NTField2DModel::ObjectModel::SetPoses(std::span<const frc::Pose2d> poses) {
|
||||
m_poses.assign(poses.begin(), poses.end());
|
||||
UpdateNT();
|
||||
}
|
||||
@@ -149,69 +106,75 @@ void NTField2DModel::ObjectModel::SetRotation(size_t i, frc::Rotation2d rot) {
|
||||
}
|
||||
|
||||
NTField2DModel::NTField2DModel(std::string_view path)
|
||||
: NTField2DModel{nt::GetDefaultInstance(), path} {}
|
||||
: NTField2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTField2DModel::NTField2DModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
m_path{fmt::format("{}/", path)},
|
||||
m_name{m_nt.GetEntry(fmt::format("{}/.name", path))} {
|
||||
m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
|
||||
NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
|
||||
NTField2DModel::NTField2DModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_path{fmt::format("{}/", path)},
|
||||
m_inst{inst},
|
||||
m_tableSub{inst,
|
||||
{{m_path}},
|
||||
{{nt::PubSubOption::SendAll(true),
|
||||
nt::PubSubOption::Periodic(0.05)}}},
|
||||
m_nameTopic{inst.GetTopic(fmt::format("{}/.name", path))},
|
||||
m_topicListener{inst},
|
||||
m_valueListener{inst} {
|
||||
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
m_valueListener.Add(m_tableSub,
|
||||
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
|
||||
}
|
||||
|
||||
NTField2DModel::~NTField2DModel() = default;
|
||||
|
||||
void NTField2DModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
// handle publish/unpublish
|
||||
for (auto&& event : m_topicListener.ReadQueue()) {
|
||||
auto name = wpi::drop_front(event.info.name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
auto [it, match] = Find(event.info.name);
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
if (match) {
|
||||
m_objects.erase(it);
|
||||
}
|
||||
continue;
|
||||
} else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(
|
||||
event.info.name, nt::DoubleArrayTopic{event.info.topic}));
|
||||
}
|
||||
} else if (!match) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// update values
|
||||
for (auto&& event : m_valueListener.ReadQueue()) {
|
||||
// .name
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
if (event.topic == m_nameTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_nameValue = event.value.GetString();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// common case: update of existing entry; search by entry
|
||||
if (event.flags & NT_NOTIFY_UPDATE) {
|
||||
auto it = std::find_if(
|
||||
m_objects.begin(), m_objects.end(),
|
||||
[&](const auto& e) { return e->GetEntry() == event.entry; });
|
||||
if (it != m_objects.end()) {
|
||||
(*it)->NTUpdate(*event.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// handle create/delete
|
||||
std::string_view name = event.name;
|
||||
if (wpi::starts_with(name, m_path)) {
|
||||
name.remove_prefix(m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
auto [it, match] = Find(event.name);
|
||||
if (event.flags & NT_NOTIFY_DELETE) {
|
||||
if (match) {
|
||||
m_objects.erase(it);
|
||||
}
|
||||
continue;
|
||||
} else if (event.flags & NT_NOTIFY_NEW) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(event.name, event.entry));
|
||||
}
|
||||
} else if (!match) {
|
||||
continue;
|
||||
}
|
||||
if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
|
||||
(*it)->NTUpdate(*event.value);
|
||||
}
|
||||
auto it =
|
||||
std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
|
||||
return e->GetTopic().GetHandle() == event.topic;
|
||||
});
|
||||
if (it != m_objects.end()) {
|
||||
(*it)->NTUpdate(event.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NTField2DModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_nameTopic.Exists();
|
||||
}
|
||||
|
||||
bool NTField2DModel::IsReadOnly() {
|
||||
@@ -222,8 +185,9 @@ FieldObjectModel* NTField2DModel::AddFieldObject(std::string_view name) {
|
||||
auto fullName = fmt::format("{}{}", m_path, name);
|
||||
auto [it, match] = Find(fullName);
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<ObjectModel>(fullName, m_nt.GetEntry(fullName)));
|
||||
it = m_objects.emplace(it,
|
||||
std::make_unique<ObjectModel>(
|
||||
fullName, m_inst.GetDoubleArrayTopic(fullName)));
|
||||
}
|
||||
return it->get();
|
||||
}
|
||||
@@ -231,7 +195,6 @@ FieldObjectModel* NTField2DModel::AddFieldObject(std::string_view name) {
|
||||
void NTField2DModel::RemoveFieldObject(std::string_view name) {
|
||||
auto [it, match] = Find(fmt::format("{}{}", m_path, name));
|
||||
if (match) {
|
||||
nt::DeleteEntry((*it)->GetEntry());
|
||||
m_objects.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,32 +10,25 @@
|
||||
using namespace glass;
|
||||
|
||||
NTGyroModel::NTGyroModel(std::string_view path)
|
||||
: NTGyroModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTGyroModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTGyroModel::NTGyroModel(NT_Inst instance, std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_angle(m_nt.GetEntry(fmt::format("{}/Value", path))),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_angleData(fmt::format("NT_Gyro:{}", path)),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
m_nt.AddListener(m_angle);
|
||||
m_nt.AddListener(m_name);
|
||||
}
|
||||
NTGyroModel::NTGyroModel(nt::NetworkTableInstance inst, std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_angle{inst.GetDoubleTopic(fmt::format("{}/Value", path))
|
||||
.Subscribe(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe({})},
|
||||
m_angleData{fmt::format("NT_Gyro:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTGyroModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_angle) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_angleData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_angle.ReadQueue()) {
|
||||
m_angleData.SetValue(v.value, v.time);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTGyroModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_angle) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_angle.Exists();
|
||||
}
|
||||
|
||||
@@ -12,69 +12,62 @@
|
||||
using namespace glass;
|
||||
|
||||
NTMecanumDriveModel::NTMecanumDriveModel(std::string_view path)
|
||||
: NTMecanumDriveModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTMecanumDriveModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTMecanumDriveModel::NTMecanumDriveModel(NT_Inst instance,
|
||||
NTMecanumDriveModel::NTMecanumDriveModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
|
||||
m_flPercent(
|
||||
m_nt.GetEntry(fmt::format("{}/Front Left Motor Speed", path))),
|
||||
m_frPercent(
|
||||
m_nt.GetEntry(fmt::format("{}/Front Right Motor Speed", path))),
|
||||
m_rlPercent(m_nt.GetEntry(fmt::format("{}/Rear Left Motor Speed", path))),
|
||||
m_rrPercent(
|
||||
m_nt.GetEntry(fmt::format("{}/Rear Right Motor Speed", path))),
|
||||
m_nameValue(wpi::rsplit(path, '/').second),
|
||||
m_flPercentData(fmt::format("NTMcnmDriveFL:{}", path)),
|
||||
m_frPercentData(fmt::format("NTMcnmDriveFR:{}", path)),
|
||||
m_rlPercentData(fmt::format("NTMcnmDriveRL:{}", path)),
|
||||
m_rrPercentData(fmt::format("NTMcnmDriveRR:{}", path)) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
m_nt.AddListener(m_flPercent);
|
||||
m_nt.AddListener(m_frPercent);
|
||||
m_nt.AddListener(m_rlPercent);
|
||||
m_nt.AddListener(m_rrPercent);
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(0)},
|
||||
m_flPercent{
|
||||
inst.GetDoubleTopic(fmt::format("{}/Front Left Motor Speed", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_frPercent{
|
||||
inst.GetDoubleTopic(fmt::format("{}/Front Right Motor Speed", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_rlPercent{
|
||||
inst.GetDoubleTopic(fmt::format("{}/Rear Left Motor Speed", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_rrPercent{
|
||||
inst.GetDoubleTopic(fmt::format("{}/Rear Right Motor Speed", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_nameValue{wpi::rsplit(path, '/').second},
|
||||
m_flPercentData{fmt::format("NTMcnmDriveFL:{}", path)},
|
||||
m_frPercentData{fmt::format("NTMcnmDriveFR:{}", path)},
|
||||
m_rlPercentData{fmt::format("NTMcnmDriveRL:{}", path)},
|
||||
m_rrPercentData{fmt::format("NTMcnmDriveRR:{}", path)} {
|
||||
m_wheels.emplace_back("FL % Output", &m_flPercentData,
|
||||
[this](auto value) { m_flPercent.Set(value); });
|
||||
|
||||
m_wheels.emplace_back("FL % Output", &m_flPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_flPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
m_wheels.emplace_back("FR % Output", &m_frPercentData,
|
||||
[this](auto value) { m_frPercent.Set(value); });
|
||||
|
||||
m_wheels.emplace_back("FR % Output", &m_frPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_frPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
m_wheels.emplace_back("RL % Output", &m_rlPercentData,
|
||||
[this](auto value) { m_rlPercent.Set(value); });
|
||||
|
||||
m_wheels.emplace_back("RL % Output", &m_rlPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_rlPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
|
||||
m_wheels.emplace_back("RR % Output", &m_rrPercentData, [this](auto value) {
|
||||
nt::SetEntryValue(m_rrPercent, nt::NetworkTableValue::MakeDouble(value));
|
||||
});
|
||||
m_wheels.emplace_back("RR % Output", &m_rrPercentData,
|
||||
[this](auto value) { m_rrPercent.Set(value); });
|
||||
}
|
||||
|
||||
void NTMecanumDriveModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name && event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
} else if (event.entry == m_flPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_flPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_frPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_frPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_rlPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_rlPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_rrPercent && event.value &&
|
||||
event.value->IsDouble()) {
|
||||
m_rrPercentData.SetValue(event.value->GetDouble());
|
||||
} else if (event.entry == m_controllable && event.value &&
|
||||
event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_flPercent.ReadQueue()) {
|
||||
m_flPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_frPercent.ReadQueue()) {
|
||||
m_frPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_rlPercent.ReadQueue()) {
|
||||
m_rlPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_rrPercent.ReadQueue()) {
|
||||
m_rrPercentData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
|
||||
double fl = m_flPercentData.GetValue();
|
||||
@@ -88,5 +81,5 @@ void NTMecanumDriveModel::Update() {
|
||||
}
|
||||
|
||||
bool NTMecanumDriveModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_flPercent) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_flPercent.Exists();
|
||||
}
|
||||
|
||||
@@ -34,16 +34,18 @@ class NTMechanismObjectModel;
|
||||
|
||||
class NTMechanismGroupImpl final {
|
||||
public:
|
||||
NTMechanismGroupImpl(NT_Inst inst, std::string_view path,
|
||||
NTMechanismGroupImpl(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_inst{inst}, m_path{path}, m_name{name} {}
|
||||
|
||||
const char* GetName() const { return m_name.c_str(); }
|
||||
void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
|
||||
void NTUpdate(const nt::EntryNotification& event, std::string_view name);
|
||||
|
||||
void NTUpdate(const nt::TopicNotification& event, std::string_view name);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
|
||||
|
||||
protected:
|
||||
NT_Inst m_inst;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::vector<std::unique_ptr<NTMechanismObjectModel>> m_objects;
|
||||
@@ -51,14 +53,14 @@ class NTMechanismGroupImpl final {
|
||||
|
||||
class NTMechanismObjectModel final : public MechanismObjectModel {
|
||||
public:
|
||||
NTMechanismObjectModel(NT_Inst inst, std::string_view path,
|
||||
NTMechanismObjectModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_type{nt::GetEntry(inst, fmt::format("{}/.type", path))},
|
||||
m_color{nt::GetEntry(inst, fmt::format("{}/color", path))},
|
||||
m_weight{nt::GetEntry(inst, fmt::format("{}/weight", path))},
|
||||
m_angle{nt::GetEntry(inst, fmt::format("{}/angle", path))},
|
||||
m_length{nt::GetEntry(inst, fmt::format("{}/length", path))} {}
|
||||
m_typeTopic{inst.GetTopic(fmt::format("{}/.type", path))},
|
||||
m_colorTopic{inst.GetTopic(fmt::format("{}/color", path))},
|
||||
m_weightTopic{inst.GetTopic(fmt::format("{}/weight", path))},
|
||||
m_angleTopic{inst.GetTopic(fmt::format("{}/angle", path))},
|
||||
m_lengthTopic{inst.GetTopic(fmt::format("{}/length", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
@@ -72,16 +74,17 @@ class NTMechanismObjectModel final : public MechanismObjectModel {
|
||||
frc::Rotation2d GetAngle() const final { return m_angleValue; }
|
||||
units::meter_t GetLength() const final { return m_lengthValue; }
|
||||
|
||||
bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
|
||||
bool NTUpdate(const nt::TopicNotification& event, std::string_view name);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
|
||||
NT_Entry m_type;
|
||||
NT_Entry m_color;
|
||||
NT_Entry m_weight;
|
||||
NT_Entry m_angle;
|
||||
NT_Entry m_length;
|
||||
nt::Topic m_typeTopic;
|
||||
nt::Topic m_colorTopic;
|
||||
nt::Topic m_weightTopic;
|
||||
nt::Topic m_angleTopic;
|
||||
nt::Topic m_lengthTopic;
|
||||
|
||||
std::string m_typeValue;
|
||||
ImU32 m_colorValue = IM_COL32_WHITE;
|
||||
@@ -99,7 +102,7 @@ void NTMechanismGroupImpl::ForEachObject(
|
||||
}
|
||||
}
|
||||
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event,
|
||||
std::string_view name) {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
@@ -115,7 +118,7 @@ void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
|
||||
[](const auto& e, std::string_view name) { return e->GetName() < name; });
|
||||
bool match = it != m_objects.end() && (*it)->GetName() == name;
|
||||
|
||||
if (event.flags & NT_NOTIFY_NEW) {
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_objects.emplace(
|
||||
it, std::make_unique<NTMechanismObjectModel>(
|
||||
@@ -123,6 +126,7 @@ void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_objects.erase(it);
|
||||
@@ -130,43 +134,74 @@ void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
|
||||
}
|
||||
}
|
||||
|
||||
bool NTMechanismObjectModel::NTUpdate(const nt::EntryNotification& event,
|
||||
void NTMechanismGroupImpl::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view name) {
|
||||
if (name.empty()) {
|
||||
return;
|
||||
}
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(
|
||||
m_objects.begin(), m_objects.end(), name,
|
||||
[](const auto& e, std::string_view name) { return e->GetName() < name; });
|
||||
if (it != m_objects.end() && (*it)->GetName() == name) {
|
||||
(*it)->NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTMechanismObjectModel::NTUpdate(const nt::TopicNotification& event,
|
||||
std::string_view childName) {
|
||||
if (event.entry == m_type) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
|
||||
if (event.info.topic == m_typeTopic.GetHandle()) {
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
return true;
|
||||
}
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_typeValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_color) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
ConvertColor(event.value->GetString(), &m_colorValue);
|
||||
}
|
||||
} else if (event.entry == m_weight) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_weightValue = event.value->GetDouble();
|
||||
}
|
||||
} else if (event.entry == m_angle) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_angleValue = units::degree_t{event.value->GetDouble()};
|
||||
}
|
||||
} else if (event.entry == m_length) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_lengthValue = units::meter_t{event.value->GetDouble()};
|
||||
}
|
||||
} else {
|
||||
} else if (event.info.topic != m_colorTopic.GetHandle() &&
|
||||
event.info.topic != m_weightTopic.GetHandle() &&
|
||||
event.info.topic != m_angleTopic.GetHandle() &&
|
||||
event.info.topic != m_lengthTopic.GetHandle()) {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NTMechanismObjectModel::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view childName) {
|
||||
if (event.topic == m_typeTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_typeValue = event.value.GetString();
|
||||
}
|
||||
} else if (event.topic == m_colorTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
ConvertColor(event.value.GetString(), &m_colorValue);
|
||||
}
|
||||
} else if (event.topic == m_weightTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_weightValue = event.value.GetDouble();
|
||||
}
|
||||
} else if (event.topic == m_angleTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_angleValue = units::degree_t{event.value.GetDouble()};
|
||||
}
|
||||
} else if (event.topic == m_lengthTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_lengthValue = units::meter_t{event.value.GetDouble()};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
public:
|
||||
RootModel(NT_Inst inst, std::string_view path, std::string_view name)
|
||||
RootModel(nt::NetworkTableInstance inst, std::string_view path,
|
||||
std::string_view name)
|
||||
: m_group{inst, path, name},
|
||||
m_x{nt::GetEntry(inst, fmt::format("{}/x", path))},
|
||||
m_y{nt::GetEntry(inst, fmt::format("{}/y", path))} {}
|
||||
m_xTopic{inst.GetTopic(fmt::format("{}/x", path))},
|
||||
m_yTopic{inst.GetTopic(fmt::format("{}/y", path))} {}
|
||||
|
||||
const char* GetName() const final { return m_group.GetName(); }
|
||||
void ForEachObject(
|
||||
@@ -174,31 +209,24 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
|
||||
m_group.ForEachObject(func);
|
||||
}
|
||||
|
||||
bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
|
||||
bool NTUpdate(const nt::TopicNotification& event, std::string_view childName);
|
||||
void NTUpdate(const nt::ValueNotification& event, std::string_view childName);
|
||||
|
||||
frc::Translation2d GetPosition() const override { return m_pos; };
|
||||
|
||||
private:
|
||||
NTMechanismGroupImpl m_group;
|
||||
NT_Entry m_x;
|
||||
NT_Entry m_y;
|
||||
nt::Topic m_xTopic;
|
||||
nt::Topic m_yTopic;
|
||||
frc::Translation2d m_pos;
|
||||
};
|
||||
|
||||
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::EntryNotification& event,
|
||||
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::TopicNotification& event,
|
||||
std::string_view childName) {
|
||||
if ((event.flags & NT_NOTIFY_DELETE) != 0 &&
|
||||
(event.entry == m_x || event.entry == m_y)) {
|
||||
return true;
|
||||
} else if (event.entry == m_x) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_pos = frc::Translation2d{units::meter_t{event.value->GetDouble()},
|
||||
m_pos.Y()};
|
||||
}
|
||||
} else if (event.entry == m_y) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_pos = frc::Translation2d{m_pos.X(),
|
||||
units::meter_t{event.value->GetDouble()}};
|
||||
if (event.info.topic == m_xTopic.GetHandle() ||
|
||||
event.info.topic == m_yTopic.GetHandle()) {
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
@@ -206,36 +234,95 @@ bool NTMechanism2DModel::RootModel::NTUpdate(const nt::EntryNotification& event,
|
||||
return false;
|
||||
}
|
||||
|
||||
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
|
||||
: NTMechanism2DModel{nt::GetDefaultInstance(), path} {}
|
||||
void NTMechanism2DModel::RootModel::NTUpdate(const nt::ValueNotification& event,
|
||||
std::string_view childName) {
|
||||
if (event.topic == m_xTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{units::meter_t{event.value.GetDouble()},
|
||||
m_pos.Y()};
|
||||
}
|
||||
} else if (event.topic == m_yTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDouble()) {
|
||||
m_pos = frc::Translation2d{m_pos.X(),
|
||||
units::meter_t{event.value.GetDouble()}};
|
||||
}
|
||||
} else {
|
||||
m_group.NTUpdate(event, childName);
|
||||
}
|
||||
}
|
||||
|
||||
NTMechanism2DModel::NTMechanism2DModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
|
||||
: NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_path{fmt::format("{}/", path)},
|
||||
m_name{m_nt.GetEntry(fmt::format("{}/.name", path))},
|
||||
m_dimensions{m_nt.GetEntry(fmt::format("{}/dims", path))},
|
||||
m_bgColor{m_nt.GetEntry(fmt::format("{}/backgroundColor", path))},
|
||||
m_tableSub{inst,
|
||||
{{m_path}},
|
||||
{{nt::PubSubOption::SendAll(true),
|
||||
nt::PubSubOption::Periodic(0.05)}}},
|
||||
m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))},
|
||||
m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))},
|
||||
m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))},
|
||||
m_topicListener{m_inst},
|
||||
m_valueListener{m_inst},
|
||||
m_dimensionsValue{1_m, 1_m} {
|
||||
m_nt.AddListener(m_path, NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
|
||||
NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE);
|
||||
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
m_valueListener.Add(m_tableSub,
|
||||
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
|
||||
}
|
||||
|
||||
NTMechanism2DModel::~NTMechanism2DModel() = default;
|
||||
|
||||
void NTMechanism2DModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
for (auto&& event : m_topicListener.ReadQueue()) {
|
||||
auto name = wpi::drop_front(event.info.name, m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
|
||||
[](const auto& e, std::string_view name) {
|
||||
return e->GetName() < name;
|
||||
});
|
||||
bool match = it != m_roots.end() && (*it)->GetName() == name;
|
||||
|
||||
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
if (!match) {
|
||||
it = m_roots.emplace(
|
||||
it, std::make_unique<RootModel>(
|
||||
m_inst, fmt::format("{}{}", m_path, name), name));
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_roots.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto&& event : m_valueListener.ReadQueue()) {
|
||||
// .name
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
if (event.topic == m_nameTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
m_nameValue = event.value.GetString();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// dims
|
||||
if (event.entry == m_dimensions) {
|
||||
if (event.value && event.value->IsDoubleArray()) {
|
||||
auto arr = event.value->GetDoubleArray();
|
||||
if (event.topic == m_dimensionsTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsDoubleArray()) {
|
||||
auto arr = event.value.GetDoubleArray();
|
||||
if (arr.size() == 2) {
|
||||
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
|
||||
units::meter_t{arr[1]}};
|
||||
@@ -244,50 +331,16 @@ void NTMechanism2DModel::Update() {
|
||||
}
|
||||
|
||||
// backgroundColor
|
||||
if (event.entry == m_bgColor) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
ConvertColor(event.value->GetString(), &m_bgColorValue);
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view name = event.name;
|
||||
if (wpi::starts_with(name, m_path)) {
|
||||
name.remove_prefix(m_path.size());
|
||||
if (name.empty() || name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
std::string_view childName;
|
||||
std::tie(name, childName) = wpi::split(name, '/');
|
||||
if (childName.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
|
||||
[](const auto& e, std::string_view name) {
|
||||
return e->GetName() < name;
|
||||
});
|
||||
bool match = it != m_roots.end() && (*it)->GetName() == name;
|
||||
|
||||
if (event.flags & NT_NOTIFY_NEW) {
|
||||
if (!match) {
|
||||
it = m_roots.emplace(
|
||||
it,
|
||||
std::make_unique<RootModel>(
|
||||
m_nt.GetInstance(), fmt::format("{}{}", m_path, name), name));
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if ((*it)->NTUpdate(event, childName)) {
|
||||
m_roots.erase(it);
|
||||
}
|
||||
if (event.topic == m_bgColorTopic.GetHandle()) {
|
||||
if (event.value && event.value.IsString()) {
|
||||
ConvertColor(event.value.GetString(), &m_bgColorValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NTMechanism2DModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_name) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_nameTopic.Exists();
|
||||
}
|
||||
|
||||
bool NTMechanism2DModel::IsReadOnly() {
|
||||
|
||||
@@ -10,76 +10,65 @@
|
||||
using namespace glass;
|
||||
|
||||
NTPIDControllerModel::NTPIDControllerModel(std::string_view path)
|
||||
: NTPIDControllerModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTPIDControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTPIDControllerModel::NTPIDControllerModel(NT_Inst instance,
|
||||
NTPIDControllerModel::NTPIDControllerModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
|
||||
m_p(m_nt.GetEntry(fmt::format("{}/p", path))),
|
||||
m_i(m_nt.GetEntry(fmt::format("{}/i", path))),
|
||||
m_d(m_nt.GetEntry(fmt::format("{}/d", path))),
|
||||
m_setpoint(m_nt.GetEntry(fmt::format("{}/setpoint", path))),
|
||||
m_pData(fmt::format("NTPIDCtrlP:{}", path)),
|
||||
m_iData(fmt::format("NTPIDCtrlI:{}", path)),
|
||||
m_dData(fmt::format("NTPIDCtrlD:{}", path)),
|
||||
m_setpointData(fmt::format("NTPIDCtrlStpt:{}", path)),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
m_nt.AddListener(m_p);
|
||||
m_nt.AddListener(m_i);
|
||||
m_nt.AddListener(m_d);
|
||||
m_nt.AddListener(m_setpoint);
|
||||
}
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(false)},
|
||||
m_p{inst.GetDoubleTopic(fmt::format("{}/p", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_i{inst.GetDoubleTopic(fmt::format("{}/i", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_d{inst.GetDoubleTopic(fmt::format("{}/d", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_setpoint{inst.GetDoubleTopic(fmt::format("{}/setpoint", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_pData{fmt::format("NTPIDCtrlP:{}", path)},
|
||||
m_iData{fmt::format("NTPIDCtrlI:{}", path)},
|
||||
m_dData{fmt::format("NTPIDCtrlD:{}", path)},
|
||||
m_setpointData{fmt::format("NTPIDCtrlStpt:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTPIDControllerModel::SetP(double value) {
|
||||
nt::SetEntryValue(m_p, nt::NetworkTableValue::MakeDouble(value));
|
||||
m_p.Set(value);
|
||||
}
|
||||
|
||||
void NTPIDControllerModel::SetI(double value) {
|
||||
nt::SetEntryValue(m_i, nt::NetworkTableValue::MakeDouble(value));
|
||||
m_i.Set(value);
|
||||
}
|
||||
|
||||
void NTPIDControllerModel::SetD(double value) {
|
||||
nt::SetEntryValue(m_d, nt::NetworkTableValue::MakeDouble(value));
|
||||
m_d.Set(value);
|
||||
}
|
||||
|
||||
void NTPIDControllerModel::SetSetpoint(double value) {
|
||||
nt::SetEntryValue(m_setpoint, nt::NetworkTableValue::MakeDouble(value));
|
||||
m_setpoint.Set(value);
|
||||
}
|
||||
|
||||
void NTPIDControllerModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_p) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_pData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_i) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_iData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_d) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_dData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_setpoint) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_setpointData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_controllable) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_p.ReadQueue()) {
|
||||
m_pData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_i.ReadQueue()) {
|
||||
m_iData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_d.ReadQueue()) {
|
||||
m_dData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_setpoint.ReadQueue()) {
|
||||
m_setpointData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NTPIDControllerModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_setpoint) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_setpoint.Exists();
|
||||
}
|
||||
|
||||
@@ -10,43 +10,35 @@
|
||||
using namespace glass;
|
||||
|
||||
NTSpeedControllerModel::NTSpeedControllerModel(std::string_view path)
|
||||
: NTSpeedControllerModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTSpeedControllerModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTSpeedControllerModel::NTSpeedControllerModel(NT_Inst instance,
|
||||
NTSpeedControllerModel::NTSpeedControllerModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_value(m_nt.GetEntry(fmt::format("{}/Value", path))),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_controllable(m_nt.GetEntry(fmt::format("{}/.controllable", path))),
|
||||
m_valueData(fmt::format("NT_SpdCtrl:{}", path)),
|
||||
m_nameValue(wpi::rsplit(path, '/').second) {
|
||||
m_nt.AddListener(m_value);
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_controllable);
|
||||
}
|
||||
: m_inst{inst},
|
||||
m_value{inst.GetDoubleTopic(fmt::format("{}/Value", path))
|
||||
.GetEntry(0, {{nt::PubSubOption::SendAll(true)}})},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_controllable{inst.GetBooleanTopic(fmt::format("{}/.controllable", path))
|
||||
.Subscribe(false)},
|
||||
m_valueData{fmt::format("NT_SpdCtrl:{}", path)},
|
||||
m_nameValue{wpi::rsplit(path, '/').second} {}
|
||||
|
||||
void NTSpeedControllerModel::SetPercent(double value) {
|
||||
nt::SetEntryValue(m_value, nt::NetworkTableValue::MakeDouble(value));
|
||||
m_value.Set(value);
|
||||
}
|
||||
|
||||
void NTSpeedControllerModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_value) {
|
||||
if (event.value && event.value->IsDouble()) {
|
||||
m_valueData.SetValue(event.value->GetDouble());
|
||||
}
|
||||
} else if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_controllable) {
|
||||
if (event.value && event.value->IsBoolean()) {
|
||||
m_controllableValue = event.value->GetBoolean();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_value.ReadQueue()) {
|
||||
m_valueData.SetValue(v.value, v.time);
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_controllable.ReadQueue()) {
|
||||
m_controllableValue = v.value;
|
||||
}
|
||||
}
|
||||
|
||||
bool NTSpeedControllerModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_value) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_value.Exists();
|
||||
}
|
||||
|
||||
@@ -9,67 +9,56 @@
|
||||
using namespace glass;
|
||||
|
||||
NTStringChooserModel::NTStringChooserModel(std::string_view path)
|
||||
: NTStringChooserModel{nt::GetDefaultInstance(), path} {}
|
||||
: NTStringChooserModel{nt::NetworkTableInstance::GetDefault(), path} {}
|
||||
|
||||
NTStringChooserModel::NTStringChooserModel(NT_Inst inst, std::string_view path)
|
||||
: m_nt{inst},
|
||||
m_default{m_nt.GetEntry(fmt::format("{}/default", path))},
|
||||
m_selected{m_nt.GetEntry(fmt::format("{}/selected", path))},
|
||||
m_active{m_nt.GetEntry(fmt::format("{}/active", path))},
|
||||
m_options{m_nt.GetEntry(fmt::format("{}/options", path))} {
|
||||
m_nt.AddListener(m_default);
|
||||
m_nt.AddListener(m_selected);
|
||||
m_nt.AddListener(m_active);
|
||||
m_nt.AddListener(m_options);
|
||||
}
|
||||
|
||||
void NTStringChooserModel::SetDefault(std::string_view val) {
|
||||
nt::SetEntryValue(m_default, nt::Value::MakeString(val));
|
||||
NTStringChooserModel::NTStringChooserModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_default{
|
||||
m_inst.GetStringTopic(fmt::format("{}/default", path)).Subscribe("")},
|
||||
m_selected{
|
||||
m_inst.GetStringTopic(fmt::format("{}/selected", path)).GetEntry("")},
|
||||
m_active{
|
||||
m_inst.GetStringTopic(fmt::format("{}/active", path)).Subscribe("")},
|
||||
m_options{m_inst.GetStringArrayTopic(fmt::format("{}/options", path))
|
||||
.Subscribe({})} {
|
||||
m_selected.GetTopic().SetRetained(true);
|
||||
}
|
||||
|
||||
void NTStringChooserModel::SetSelected(std::string_view val) {
|
||||
nt::SetEntryValue(m_selected, nt::Value::MakeString(val));
|
||||
}
|
||||
|
||||
void NTStringChooserModel::SetActive(std::string_view val) {
|
||||
nt::SetEntryValue(m_active, nt::Value::MakeString(val));
|
||||
}
|
||||
|
||||
void NTStringChooserModel::SetOptions(wpi::span<const std::string> val) {
|
||||
nt::SetEntryValue(m_options, nt::Value::MakeStringArray(val));
|
||||
m_selected.Set(val);
|
||||
}
|
||||
|
||||
void NTStringChooserModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
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()) {
|
||||
auto arr = event.value->GetStringArray();
|
||||
m_optionsValue.assign(arr.begin(), arr.end());
|
||||
}
|
||||
}
|
||||
if (!m_default.Exists()) {
|
||||
m_defaultValue.clear();
|
||||
}
|
||||
for (auto&& v : m_default.ReadQueue()) {
|
||||
m_defaultValue = std::move(v.value);
|
||||
}
|
||||
|
||||
if (!m_selected.Exists()) {
|
||||
m_selectedValue.clear();
|
||||
}
|
||||
for (auto&& v : m_selected.ReadQueue()) {
|
||||
m_selectedValue = std::move(v.value);
|
||||
}
|
||||
|
||||
if (!m_active.Exists()) {
|
||||
m_activeValue.clear();
|
||||
}
|
||||
for (auto&& v : m_active.ReadQueue()) {
|
||||
m_activeValue = std::move(v.value);
|
||||
}
|
||||
|
||||
if (!m_options.Exists()) {
|
||||
m_optionsValue.clear();
|
||||
}
|
||||
for (auto&& v : m_options.ReadQueue()) {
|
||||
m_optionsValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTStringChooserModel::Exists() {
|
||||
return m_nt.IsConnected() && nt::GetEntryType(m_options) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_options.Exists();
|
||||
}
|
||||
|
||||
@@ -9,37 +9,30 @@
|
||||
using namespace glass;
|
||||
|
||||
NTSubsystemModel::NTSubsystemModel(std::string_view path)
|
||||
: NTSubsystemModel(nt::GetDefaultInstance(), path) {}
|
||||
: NTSubsystemModel(nt::NetworkTableInstance::GetDefault(), path) {}
|
||||
|
||||
NTSubsystemModel::NTSubsystemModel(NT_Inst instance, std::string_view path)
|
||||
: m_nt(instance),
|
||||
m_name(m_nt.GetEntry(fmt::format("{}/.name", path))),
|
||||
m_defaultCommand(m_nt.GetEntry(fmt::format("{}/.default", path))),
|
||||
m_currentCommand(m_nt.GetEntry(fmt::format("{}/.command", path))) {
|
||||
m_nt.AddListener(m_name);
|
||||
m_nt.AddListener(m_defaultCommand);
|
||||
m_nt.AddListener(m_currentCommand);
|
||||
NTSubsystemModel::NTSubsystemModel(nt::NetworkTableInstance inst,
|
||||
std::string_view path)
|
||||
: m_inst{inst},
|
||||
m_name{inst.GetStringTopic(fmt::format("{}/.name", path)).Subscribe("")},
|
||||
m_defaultCommand{
|
||||
inst.GetStringTopic(fmt::format("{}/.default", path)).Subscribe("")},
|
||||
m_currentCommand{
|
||||
inst.GetStringTopic(fmt::format("{}/.command", path)).Subscribe("")} {
|
||||
}
|
||||
|
||||
void NTSubsystemModel::Update() {
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
if (event.entry == m_name) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_nameValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_defaultCommand) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_defaultCommandValue = event.value->GetString();
|
||||
}
|
||||
} else if (event.entry == m_currentCommand) {
|
||||
if (event.value && event.value->IsString()) {
|
||||
m_currentCommandValue = event.value->GetString();
|
||||
}
|
||||
}
|
||||
for (auto&& v : m_name.ReadQueue()) {
|
||||
m_nameValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_defaultCommand.ReadQueue()) {
|
||||
m_defaultCommandValue = std::move(v.value);
|
||||
}
|
||||
for (auto&& v : m_currentCommand.ReadQueue()) {
|
||||
m_currentCommandValue = std::move(v.value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NTSubsystemModel::Exists() {
|
||||
return m_nt.IsConnected() &&
|
||||
nt::GetEntryType(m_defaultCommand) != NT_UNASSIGNED;
|
||||
return m_inst.IsConnected() && m_defaultCommand.Exists();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +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 "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
NetworkTablesHelper::NetworkTablesHelper(NT_Inst inst)
|
||||
: m_inst{inst}, m_poller{nt::CreateEntryListenerPoller(inst)} {}
|
||||
|
||||
NetworkTablesHelper::~NetworkTablesHelper() {
|
||||
nt::DestroyEntryListenerPoller(m_poller);
|
||||
}
|
||||
|
||||
bool NetworkTablesHelper::IsConnected() const {
|
||||
return nt::GetNetworkMode(m_inst) == NT_NET_MODE_SERVER ||
|
||||
nt::IsConnected(m_inst);
|
||||
}
|
||||
@@ -17,16 +17,19 @@
|
||||
using namespace glass;
|
||||
|
||||
NetworkTablesProvider::NetworkTablesProvider(Storage& storage)
|
||||
: NetworkTablesProvider{storage, nt::GetDefaultInstance()} {}
|
||||
: NetworkTablesProvider{storage, nt::NetworkTableInstance::GetDefault()} {}
|
||||
|
||||
NetworkTablesProvider::NetworkTablesProvider(Storage& storage, NT_Inst inst)
|
||||
NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
|
||||
nt::NetworkTableInstance inst)
|
||||
: Provider{storage.GetChild("windows")},
|
||||
m_nt{inst},
|
||||
m_inst{inst},
|
||||
m_topicPoller{inst},
|
||||
m_valuePoller{inst},
|
||||
m_typeCache{storage.GetChild("types")} {
|
||||
storage.SetCustomApply([this] {
|
||||
m_listener =
|
||||
m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW |
|
||||
NT_NOTIFY_DELETE | NT_NOTIFY_IMMEDIATE);
|
||||
m_topicListener = m_topicPoller.Add({{""}}, NT_TOPIC_NOTIFY_PUBLISH |
|
||||
NT_TOPIC_NOTIFY_UNPUBLISH |
|
||||
NT_TOPIC_NOTIFY_IMMEDIATE);
|
||||
for (auto&& childIt : m_storage.GetChildren()) {
|
||||
auto id = childIt.key();
|
||||
auto typePtr = m_typeCache.FindValue(id);
|
||||
@@ -41,16 +44,15 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage, NT_Inst inst)
|
||||
}
|
||||
|
||||
auto entry = GetOrCreateView(
|
||||
builderIt->second,
|
||||
nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id);
|
||||
builderIt->second, m_inst.GetTopic(fmt::format("{}/.type", id)), id);
|
||||
if (entry) {
|
||||
Show(entry, nullptr);
|
||||
}
|
||||
}
|
||||
});
|
||||
storage.SetCustomClear([this, &storage] {
|
||||
nt::RemoveEntryListener(m_listener);
|
||||
m_listener = 0;
|
||||
m_topicPoller.Remove(m_topicListener);
|
||||
m_topicListener = 0;
|
||||
for (auto&& modelEntry : m_modelEntries) {
|
||||
modelEntry->model.reset();
|
||||
}
|
||||
@@ -101,35 +103,57 @@ void NetworkTablesProvider::Update() {
|
||||
Provider::Update();
|
||||
|
||||
// add/remove entries from NT changes
|
||||
for (auto&& event : m_nt.PollListener()) {
|
||||
for (auto&& event : m_topicPoller.ReadQueue()) {
|
||||
// look for .type fields
|
||||
std::string_view eventName{event.name};
|
||||
if (!wpi::ends_with(eventName, "/.type") || !event.value ||
|
||||
!event.value->IsString()) {
|
||||
if (!wpi::ends_with(event.info.name, "/.type") ||
|
||||
event.info.type != NT_STRING || event.info.type_str != "string") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
|
||||
auto it = m_topicMap.find(event.info.topic);
|
||||
if (it != m_topicMap.end()) {
|
||||
m_valuePoller.Remove(it->second.listener);
|
||||
m_topicMap.erase(it);
|
||||
}
|
||||
|
||||
auto it2 = std::find_if(
|
||||
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
|
||||
return static_cast<Entry*>(elem->modelEntry)
|
||||
->typeTopic.GetHandle() == event.info.topic;
|
||||
});
|
||||
if (it2 != m_viewEntries.end()) {
|
||||
m_viewEntries.erase(it2);
|
||||
}
|
||||
} else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
|
||||
// subscribe to it
|
||||
SubListener sublistener;
|
||||
sublistener.subscriber = nt::StringTopic{event.info.topic}.Subscribe("");
|
||||
sublistener.listener =
|
||||
m_valuePoller.Add(sublistener.subscriber,
|
||||
NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE);
|
||||
m_topicMap.try_emplace(event.info.topic, std::move(sublistener));
|
||||
}
|
||||
}
|
||||
|
||||
// handle actual .type strings
|
||||
for (auto&& event : m_valuePoller.ReadQueue()) {
|
||||
if (!event.value.IsString()) {
|
||||
continue;
|
||||
}
|
||||
auto tableName = wpi::drop_back(eventName, 6);
|
||||
|
||||
// only handle ones where we have a builder
|
||||
auto builderIt = m_typeMap.find(event.value->GetString());
|
||||
auto builderIt = m_typeMap.find(event.value.GetString());
|
||||
if (builderIt == m_typeMap.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.flags & NT_NOTIFY_DELETE) {
|
||||
auto it = std::find_if(
|
||||
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
|
||||
return static_cast<Entry*>(elem->modelEntry)->typeEntry ==
|
||||
event.entry;
|
||||
});
|
||||
if (it != m_viewEntries.end()) {
|
||||
m_viewEntries.erase(it);
|
||||
}
|
||||
} else if (event.flags & NT_NOTIFY_NEW) {
|
||||
GetOrCreateView(builderIt->second, event.entry, tableName);
|
||||
// cache the type
|
||||
m_typeCache.SetString(tableName, event.value->GetString());
|
||||
}
|
||||
auto topicName = nt::GetTopicName(event.topic);
|
||||
auto tableName = wpi::drop_back(topicName, 6);
|
||||
|
||||
GetOrCreateView(builderIt->second, nt::Topic{event.topic}, tableName);
|
||||
// cache the type
|
||||
m_typeCache.SetString(tableName, event.value.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +173,7 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
|
||||
// get or create model
|
||||
if (!entry->modelEntry->model) {
|
||||
entry->modelEntry->model =
|
||||
entry->modelEntry->createModel(m_nt.GetInstance(), entry->name.c_str());
|
||||
entry->modelEntry->createModel(m_inst, entry->name.c_str());
|
||||
}
|
||||
if (!entry->modelEntry->model) {
|
||||
return;
|
||||
@@ -180,22 +204,22 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
|
||||
}
|
||||
|
||||
NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
|
||||
const Builder& builder, NT_Entry typeEntry, std::string_view name) {
|
||||
const Builder& builder, nt::Topic typeTopic, std::string_view name) {
|
||||
// get view entry if it already exists
|
||||
auto viewIt = FindViewEntry(name);
|
||||
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
|
||||
// make sure typeEntry is set in model
|
||||
static_cast<Entry*>((*viewIt)->modelEntry)->typeEntry = typeEntry;
|
||||
static_cast<Entry*>((*viewIt)->modelEntry)->typeTopic = typeTopic;
|
||||
return viewIt->get();
|
||||
}
|
||||
|
||||
// get or create model entry
|
||||
auto modelIt = FindModelEntry(name);
|
||||
if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
|
||||
static_cast<Entry*>(modelIt->get())->typeEntry = typeEntry;
|
||||
static_cast<Entry*>(modelIt->get())->typeTopic = typeTopic;
|
||||
} else {
|
||||
modelIt = m_modelEntries.emplace(
|
||||
modelIt, std::make_unique<Entry>(typeEntry, name, builder));
|
||||
modelIt, std::make_unique<Entry>(typeTopic, name, builder));
|
||||
}
|
||||
|
||||
// create new view entry
|
||||
|
||||
@@ -43,50 +43,62 @@ void NetworkTablesSettings::Thread::Main() {
|
||||
|
||||
// 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) {
|
||||
if ((mode == 0 || mode == 3) ||
|
||||
(mode == 1 && (curMode & NT_NET_MODE_CLIENT4) == 0) ||
|
||||
(mode == 2 && (curMode & NT_NET_MODE_CLIENT3) == 0)) {
|
||||
nt::StopClient(m_inst);
|
||||
nt::StopServer(m_inst);
|
||||
nt::StopLocal(m_inst);
|
||||
}
|
||||
|
||||
if (m_mode != 1 || !dsClient) {
|
||||
if ((m_mode == 0 || m_mode == 3) || !dsClient) {
|
||||
nt::StopDSClient(m_inst);
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
} while (mode != m_mode || dsClient != m_dsClient);
|
||||
|
||||
if (m_mode == 1) {
|
||||
if (m_mode == 1 || m_mode == 2) {
|
||||
std::string_view serverTeam{m_serverTeam};
|
||||
std::optional<unsigned int> team;
|
||||
if (m_mode == 1) {
|
||||
nt::StartClient4(m_inst, m_clientName);
|
||||
} else if (m_mode == 2) {
|
||||
nt::StartClient3(m_inst, m_clientName);
|
||||
}
|
||||
if (!wpi::contains(serverTeam, '.') &&
|
||||
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
|
||||
nt::StartClientTeam(m_inst, team.value(), NT_DEFAULT_PORT);
|
||||
nt::SetServerTeam(m_inst, team.value(), 0);
|
||||
} else {
|
||||
wpi::SmallVector<std::string_view, 4> serverNames;
|
||||
wpi::SmallVector<std::pair<std::string_view, unsigned int>, 4> servers;
|
||||
std::vector<std::pair<std::string_view, unsigned int>> servers;
|
||||
wpi::split(serverTeam, serverNames, ',', -1, false);
|
||||
for (auto&& serverName : serverNames) {
|
||||
servers.emplace_back(serverName, NT_DEFAULT_PORT);
|
||||
servers.emplace_back(serverName, 0);
|
||||
}
|
||||
nt::StartClient(m_inst, servers);
|
||||
nt::SetServer(m_inst, servers);
|
||||
}
|
||||
|
||||
if (m_dsClient) {
|
||||
nt::StartDSClient(m_inst, NT_DEFAULT_PORT);
|
||||
nt::StartDSClient(m_inst, 0);
|
||||
}
|
||||
} else if (m_mode == 2) {
|
||||
} else if (m_mode == 3) {
|
||||
nt::StartServer(m_inst, m_iniName.c_str(), m_listenAddress.c_str(),
|
||||
NT_DEFAULT_PORT);
|
||||
NT_DEFAULT_PORT3, NT_DEFAULT_PORT4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkTablesSettings::NetworkTablesSettings(Storage& storage, NT_Inst inst)
|
||||
: m_mode{storage.GetString("mode"), 0, {"Disabled", "Client", "Server"}},
|
||||
m_iniName{storage.GetString("iniName", "networktables.ini")},
|
||||
NetworkTablesSettings::NetworkTablesSettings(std::string_view clientName,
|
||||
Storage& storage, NT_Inst inst)
|
||||
: m_mode{storage.GetString("mode"),
|
||||
0,
|
||||
{"Disabled", "Client (NT4)", "Client (NT3)", "Server"}},
|
||||
m_persistentFilename{
|
||||
storage.GetString("persistentFilename", "networktables.json")},
|
||||
m_serverTeam{storage.GetString("serverTeam")},
|
||||
m_listenAddress{storage.GetString("listenAddress")},
|
||||
m_clientName{storage.GetString("clientName", clientName)},
|
||||
m_dsClient{storage.GetBool("dsClient", true)} {
|
||||
m_thread.Start(inst);
|
||||
}
|
||||
@@ -101,23 +113,26 @@ void NetworkTablesSettings::Update() {
|
||||
auto thr = m_thread.GetThread();
|
||||
thr->m_restart = true;
|
||||
thr->m_mode = m_mode.GetValue();
|
||||
thr->m_iniName = m_iniName;
|
||||
thr->m_iniName = m_persistentFilename;
|
||||
thr->m_serverTeam = m_serverTeam;
|
||||
thr->m_listenAddress = m_listenAddress;
|
||||
thr->m_clientName = m_clientName;
|
||||
thr->m_dsClient = m_dsClient;
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
bool NetworkTablesSettings::Display() {
|
||||
m_mode.Combo("Mode", m_serverOption ? 3 : 2);
|
||||
m_mode.Combo("Mode", m_serverOption ? 4 : 3);
|
||||
switch (m_mode.GetValue()) {
|
||||
case 1:
|
||||
case 2:
|
||||
ImGui::InputText("Team/IP", &m_serverTeam);
|
||||
ImGui::InputText("Network Identity", &m_clientName);
|
||||
ImGui::Checkbox("Get Address from DS", &m_dsClient);
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
ImGui::InputText("Listen Address", &m_listenAddress);
|
||||
ImGui::InputText("ini Filename", &m_iniName);
|
||||
ImGui::InputText("Persistent Filename", &m_persistentFilename);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -23,7 +23,7 @@ using namespace glass;
|
||||
void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
provider.Register(
|
||||
NTCommandSchedulerModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTCommandSchedulerModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -34,7 +34,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTCommandSelectorModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTCommandSelectorModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -45,7 +45,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTDifferentialDriveModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTDifferentialDriveModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -56,7 +56,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTFMSModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTFMSModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -66,7 +66,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTDigitalInputModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTDigitalInputModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -77,7 +77,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTDigitalOutputModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTDigitalOutputModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -88,7 +88,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTField2DModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTField2DModel>(inst, path);
|
||||
},
|
||||
[=](Window* win, Model* model, const char* path) {
|
||||
@@ -100,7 +100,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTGyroModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTGyroModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char* path) {
|
||||
@@ -110,7 +110,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTMecanumDriveModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTMecanumDriveModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -120,7 +120,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTMechanism2DModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTMechanism2DModel>(inst, path);
|
||||
},
|
||||
[=](Window* win, Model* model, const char* path) {
|
||||
@@ -132,7 +132,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTPIDControllerModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTPIDControllerModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char* path) {
|
||||
@@ -143,7 +143,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTSpeedControllerModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTSpeedControllerModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char* path) {
|
||||
@@ -154,7 +154,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTStringChooserModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTStringChooserModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
@@ -165,7 +165,7 @@ void glass::AddStandardNetworkTablesViews(NetworkTablesProvider& provider) {
|
||||
});
|
||||
provider.Register(
|
||||
NTSubsystemModel::kType,
|
||||
[](NT_Inst inst, const char* path) {
|
||||
[](nt::NetworkTableInstance inst, const char* path) {
|
||||
return std::make_unique<NTSubsystemModel>(inst, path);
|
||||
},
|
||||
[](Window* win, Model* model, const char*) {
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/IntegerArrayTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringArrayTopic.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/CommandScheduler.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -20,7 +24,7 @@ class NTCommandSchedulerModel : public CommandSchedulerModel {
|
||||
static constexpr const char* kType = "Scheduler";
|
||||
|
||||
explicit NTCommandSchedulerModel(std::string_view path);
|
||||
NTCommandSchedulerModel(NT_Inst instance, std::string_view path);
|
||||
NTCommandSchedulerModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const std::vector<std::string>& GetCurrentCommands() override {
|
||||
@@ -34,14 +38,14 @@ class NTCommandSchedulerModel : public CommandSchedulerModel {
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_commands;
|
||||
NT_Entry m_ids;
|
||||
NT_Entry m_cancel;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::StringArraySubscriber m_commands;
|
||||
nt::IntegerArraySubscriber m_ids;
|
||||
nt::IntegerArrayPublisher m_cancel;
|
||||
|
||||
std::string m_nameValue;
|
||||
std::vector<std::string> m_commandsValue;
|
||||
std::vector<double> m_idsValue;
|
||||
std::vector<int64_t> m_idsValue;
|
||||
};
|
||||
} // namespace glass
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/CommandSelector.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -19,7 +20,7 @@ class NTCommandSelectorModel : public CommandSelectorModel {
|
||||
static constexpr const char* kType = "Command";
|
||||
|
||||
explicit NTCommandSelectorModel(std::string_view path);
|
||||
NTCommandSelectorModel(NT_Inst instance, std::string_view path);
|
||||
NTCommandSelectorModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
DataSource* GetRunningData() override { return &m_runningData; }
|
||||
@@ -30,9 +31,9 @@ class NTCommandSelectorModel : public CommandSelectorModel {
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_running;
|
||||
NT_Entry m_name;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::BooleanEntry m_running;
|
||||
nt::StringSubscriber m_name;
|
||||
|
||||
DataSource m_runningData;
|
||||
std::string m_nameValue;
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/DoubleTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/Drive.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -20,7 +22,8 @@ class NTDifferentialDriveModel : public DriveModel {
|
||||
static constexpr const char* kType = "DifferentialDrive";
|
||||
|
||||
explicit NTDifferentialDriveModel(std::string_view path);
|
||||
NTDifferentialDriveModel(NT_Inst instance, std::string_view path);
|
||||
NTDifferentialDriveModel(nt::NetworkTableInstance instance,
|
||||
std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const std::vector<DriveModel::WheelInfo>& GetWheels() const override {
|
||||
@@ -35,11 +38,11 @@ class NTDifferentialDriveModel : public DriveModel {
|
||||
bool IsReadOnly() override { return !m_controllableValue; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_controllable;
|
||||
NT_Entry m_lPercent;
|
||||
NT_Entry m_rPercent;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::BooleanSubscriber m_controllable;
|
||||
nt::DoubleEntry m_lPercent;
|
||||
nt::DoubleEntry m_rPercent;
|
||||
|
||||
std::string m_nameValue;
|
||||
bool m_controllableValue = false;
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/DIO.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -21,7 +22,7 @@ class NTDigitalInputModel : public DIOModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTDigitalInputModel(std::string_view path);
|
||||
NTDigitalInputModel(NT_Inst inst, std::string_view path);
|
||||
NTDigitalInputModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
|
||||
@@ -42,9 +43,9 @@ class NTDigitalInputModel : public DIOModel {
|
||||
bool IsReadOnly() override { return true; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_value;
|
||||
NT_Entry m_name;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::BooleanSubscriber m_value;
|
||||
nt::StringSubscriber m_name;
|
||||
|
||||
DataSource m_valueData;
|
||||
std::string m_nameValue;
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/DIO.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -21,7 +22,7 @@ class NTDigitalOutputModel : public DIOModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTDigitalOutputModel(std::string_view path);
|
||||
NTDigitalOutputModel(NT_Inst inst, std::string_view path);
|
||||
NTDigitalOutputModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
|
||||
@@ -42,10 +43,10 @@ class NTDigitalOutputModel : public DIOModel {
|
||||
bool IsReadOnly() override { return !m_controllableValue; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_value;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_controllable;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::BooleanEntry m_value;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::BooleanSubscriber m_controllable;
|
||||
|
||||
DataSource m_valueData;
|
||||
std::string m_nameValue;
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/IntegerTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/FMS.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -20,7 +22,7 @@ class NTFMSModel : public FMSModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTFMSModel(std::string_view path);
|
||||
NTFMSModel(NT_Inst inst, std::string_view path);
|
||||
NTFMSModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
DataSource* GetFmsAttachedData() override { return &m_fmsAttached; }
|
||||
DataSource* GetDsAttachedData() override { return &m_dsAttached; }
|
||||
@@ -52,11 +54,11 @@ class NTFMSModel : public FMSModel {
|
||||
bool IsReadOnly() override { return true; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_gameSpecificMessage;
|
||||
NT_Entry m_alliance;
|
||||
NT_Entry m_station;
|
||||
NT_Entry m_controlWord;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_gameSpecificMessage;
|
||||
nt::BooleanSubscriber m_alliance;
|
||||
nt::IntegerSubscriber m_station;
|
||||
nt::IntegerSubscriber m_controlWord;
|
||||
|
||||
DataSource m_fmsAttached;
|
||||
DataSource m_dsAttached;
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/MultiSubscriber.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <ntcore_cpp.h>
|
||||
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/Field2D.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -23,7 +27,7 @@ class NTField2DModel : public Field2DModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTField2DModel(std::string_view path);
|
||||
NTField2DModel(NT_Inst inst, std::string_view path);
|
||||
NTField2DModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
~NTField2DModel() override;
|
||||
|
||||
const char* GetPath() const { return m_path.c_str(); }
|
||||
@@ -40,9 +44,12 @@ class NTField2DModel : public Field2DModel {
|
||||
func) override;
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
std::string m_path;
|
||||
NT_Entry m_name;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::MultiSubscriber m_tableSub;
|
||||
nt::StringTopic m_nameTopic;
|
||||
nt::TopicListenerPoller m_topicListener;
|
||||
nt::ValueListenerPoller m_valueListener;
|
||||
std::string m_nameValue;
|
||||
|
||||
class ObjectModel;
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/DoubleTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/Gyro.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
namespace glass {
|
||||
class NTGyroModel : public GyroModel {
|
||||
@@ -19,7 +20,7 @@ class NTGyroModel : public GyroModel {
|
||||
static constexpr const char* kType = "Gyro";
|
||||
|
||||
explicit NTGyroModel(std::string_view path);
|
||||
NTGyroModel(NT_Inst instance, std::string_view path);
|
||||
NTGyroModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const char* GetSimDevice() const override { return nullptr; }
|
||||
@@ -32,9 +33,9 @@ class NTGyroModel : public GyroModel {
|
||||
bool IsReadOnly() override { return true; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_angle;
|
||||
NT_Entry m_name;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::DoubleSubscriber m_angle;
|
||||
nt::StringSubscriber m_name;
|
||||
|
||||
DataSource m_angleData;
|
||||
std::string m_nameValue;
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/DoubleTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/Drive.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -20,7 +22,7 @@ class NTMecanumDriveModel : public DriveModel {
|
||||
static constexpr const char* kType = "MecanumDrive";
|
||||
|
||||
explicit NTMecanumDriveModel(std::string_view path);
|
||||
NTMecanumDriveModel(NT_Inst instance, std::string_view path);
|
||||
NTMecanumDriveModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const std::vector<DriveModel::WheelInfo>& GetWheels() const override {
|
||||
@@ -35,13 +37,13 @@ class NTMecanumDriveModel : public DriveModel {
|
||||
bool IsReadOnly() override { return !m_controllableValue; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_controllable;
|
||||
NT_Entry m_flPercent;
|
||||
NT_Entry m_frPercent;
|
||||
NT_Entry m_rlPercent;
|
||||
NT_Entry m_rrPercent;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::BooleanSubscriber m_controllable;
|
||||
nt::DoubleEntry m_flPercent;
|
||||
nt::DoubleEntry m_frPercent;
|
||||
nt::DoubleEntry m_rlPercent;
|
||||
nt::DoubleEntry m_rrPercent;
|
||||
|
||||
std::string m_nameValue;
|
||||
bool m_controllableValue = false;
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <vector>
|
||||
|
||||
#include <frc/geometry/Translation2d.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/MultiSubscriber.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/Mechanism2D.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -24,7 +26,7 @@ class NTMechanism2DModel : public Mechanism2DModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTMechanism2DModel(std::string_view path);
|
||||
NTMechanism2DModel(NT_Inst inst, std::string_view path);
|
||||
NTMechanism2DModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
~NTMechanism2DModel() override;
|
||||
|
||||
const char* GetPath() const { return m_path.c_str(); }
|
||||
@@ -42,12 +44,14 @@ class NTMechanism2DModel : public Mechanism2DModel {
|
||||
wpi::function_ref<void(MechanismRootModel& model)> func) override;
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
std::string m_path;
|
||||
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_dimensions;
|
||||
NT_Entry m_bgColor;
|
||||
nt::MultiSubscriber m_tableSub;
|
||||
nt::Topic m_nameTopic;
|
||||
nt::Topic m_dimensionsTopic;
|
||||
nt::Topic m_bgColorTopic;
|
||||
nt::TopicListenerPoller m_topicListener;
|
||||
nt::ValueListenerPoller m_valueListener;
|
||||
|
||||
std::string m_nameValue;
|
||||
frc::Translation2d m_dimensionsValue;
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/DoubleTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/PIDController.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -19,7 +21,7 @@ class NTPIDControllerModel : public PIDControllerModel {
|
||||
static constexpr const char* kType = "PIDController";
|
||||
|
||||
explicit NTPIDControllerModel(std::string_view path);
|
||||
NTPIDControllerModel(NT_Inst instance, std::string_view path);
|
||||
NTPIDControllerModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
|
||||
@@ -38,13 +40,13 @@ class NTPIDControllerModel : public PIDControllerModel {
|
||||
bool IsReadOnly() override { return !m_controllableValue; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_controllable;
|
||||
NT_Entry m_p;
|
||||
NT_Entry m_i;
|
||||
NT_Entry m_d;
|
||||
NT_Entry m_setpoint;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::BooleanSubscriber m_controllable;
|
||||
nt::DoubleEntry m_p;
|
||||
nt::DoubleEntry m_i;
|
||||
nt::DoubleEntry m_d;
|
||||
nt::DoubleEntry m_setpoint;
|
||||
|
||||
DataSource m_pData;
|
||||
DataSource m_iData;
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/BooleanTopic.h>
|
||||
#include <networktables/DoubleTopic.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/SpeedController.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
namespace glass {
|
||||
class NTSpeedControllerModel : public SpeedControllerModel {
|
||||
@@ -19,7 +21,7 @@ class NTSpeedControllerModel : public SpeedControllerModel {
|
||||
static constexpr const char* kType = "Motor Controller";
|
||||
|
||||
explicit NTSpeedControllerModel(std::string_view path);
|
||||
NTSpeedControllerModel(NT_Inst instance, std::string_view path);
|
||||
NTSpeedControllerModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const char* GetSimDevice() const override { return nullptr; }
|
||||
@@ -32,10 +34,10 @@ class NTSpeedControllerModel : public SpeedControllerModel {
|
||||
bool IsReadOnly() override { return !m_controllableValue; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_value;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_controllable;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::DoubleEntry m_value;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::BooleanSubscriber m_controllable;
|
||||
|
||||
DataSource m_valueData;
|
||||
std::string m_nameValue;
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringArrayTopic.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/StringChooser.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -20,7 +21,7 @@ class NTStringChooserModel : public StringChooserModel {
|
||||
|
||||
// path is to the table containing ".type", excluding the trailing /
|
||||
explicit NTStringChooserModel(std::string_view path);
|
||||
NTStringChooserModel(NT_Inst inst, std::string_view path);
|
||||
NTStringChooserModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const std::string& GetDefault() override { return m_defaultValue; }
|
||||
const std::string& GetSelected() override { return m_selectedValue; }
|
||||
@@ -29,21 +30,18 @@ class NTStringChooserModel : public StringChooserModel {
|
||||
return m_optionsValue;
|
||||
}
|
||||
|
||||
void SetDefault(std::string_view val) override;
|
||||
void SetSelected(std::string_view val) override;
|
||||
void SetActive(std::string_view val) override;
|
||||
void SetOptions(wpi::span<const std::string> val) override;
|
||||
|
||||
void Update() override;
|
||||
bool Exists() override;
|
||||
bool IsReadOnly() override { return false; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_default;
|
||||
NT_Entry m_selected;
|
||||
NT_Entry m_active;
|
||||
NT_Entry m_options;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_default;
|
||||
nt::StringEntry m_selected;
|
||||
nt::StringSubscriber m_active;
|
||||
nt::StringArraySubscriber m_options;
|
||||
|
||||
std::string m_defaultValue;
|
||||
std::string m_selectedValue;
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
#include "glass/other/Subsystem.h"
|
||||
|
||||
namespace glass {
|
||||
@@ -19,7 +19,7 @@ class NTSubsystemModel : public SubsystemModel {
|
||||
static constexpr const char* kType = "Subsystem";
|
||||
|
||||
explicit NTSubsystemModel(std::string_view path);
|
||||
NTSubsystemModel(NT_Inst instance, std::string_view path);
|
||||
NTSubsystemModel(nt::NetworkTableInstance inst, std::string_view path);
|
||||
|
||||
const char* GetName() const override { return m_nameValue.c_str(); }
|
||||
const char* GetDefaultCommand() const override {
|
||||
@@ -34,10 +34,10 @@ class NTSubsystemModel : public SubsystemModel {
|
||||
bool IsReadOnly() override { return true; }
|
||||
|
||||
private:
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_Entry m_name;
|
||||
NT_Entry m_defaultCommand;
|
||||
NT_Entry m_currentCommand;
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::StringSubscriber m_name;
|
||||
nt::StringSubscriber m_defaultCommand;
|
||||
nt::StringSubscriber m_currentCommand;
|
||||
|
||||
std::string m_nameValue;
|
||||
std::string m_defaultCommandValue;
|
||||
|
||||
@@ -4,13 +4,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/View.h"
|
||||
@@ -21,28 +29,81 @@ class DataSource;
|
||||
|
||||
class NetworkTablesModel : public Model {
|
||||
public:
|
||||
struct Entry {
|
||||
explicit Entry(nt::EntryNotification&& event);
|
||||
struct SubscriberOptions {
|
||||
float periodic = 0.1f;
|
||||
bool immediate = false;
|
||||
bool sendAll = false;
|
||||
bool prefixMatch = false;
|
||||
// std::string otherStr;
|
||||
};
|
||||
|
||||
void UpdateValue();
|
||||
struct TopicPublisher {
|
||||
std::string client;
|
||||
uint64_t pubuid;
|
||||
};
|
||||
|
||||
/** Entry handle. */
|
||||
NT_Entry entry;
|
||||
struct TopicSubscriber {
|
||||
std::string client;
|
||||
uint64_t subuid;
|
||||
SubscriberOptions options;
|
||||
};
|
||||
|
||||
/** Entry name. */
|
||||
std::string name;
|
||||
struct EntryValueTreeNode;
|
||||
|
||||
/** The value. */
|
||||
std::shared_ptr<nt::Value> value;
|
||||
struct ValueSource {
|
||||
void UpdateFromValue(nt::Value&& v, std::string_view name,
|
||||
std::string_view typeStr);
|
||||
|
||||
/** Flags. */
|
||||
unsigned int flags = 0;
|
||||
/** The latest value. */
|
||||
nt::Value value;
|
||||
|
||||
/** String representation of the value (for arrays / complex values). */
|
||||
std::string valueStr;
|
||||
|
||||
/** Data source (for numeric values). */
|
||||
std::unique_ptr<DataSource> source;
|
||||
|
||||
/** Children of this node, sorted by name/index */
|
||||
std::vector<EntryValueTreeNode> valueChildren;
|
||||
|
||||
/** Whether or not the children represent a map */
|
||||
bool valueChildrenMap = false;
|
||||
};
|
||||
|
||||
struct EntryValueTreeNode : public ValueSource {
|
||||
/** Short name (e.g. of just this node) */
|
||||
std::string name;
|
||||
|
||||
/** Full path */
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct Entry : public ValueSource {
|
||||
Entry() = default;
|
||||
Entry(const Entry&) = delete;
|
||||
Entry& operator=(const Entry&) = delete;
|
||||
~Entry();
|
||||
|
||||
void UpdateTopic(nt::TopicNotification&& event) {
|
||||
UpdateInfo(std::move(event.info));
|
||||
}
|
||||
void UpdateInfo(nt::TopicInfo&& info_);
|
||||
|
||||
/** Topic information. */
|
||||
nt::TopicInfo info;
|
||||
|
||||
/** JSON representation of the topic properties. */
|
||||
wpi::json properties;
|
||||
|
||||
/** Specific common property flags. */
|
||||
bool persistent{false};
|
||||
bool retained{false};
|
||||
|
||||
/** Publisher (created when the value changes). */
|
||||
NT_Publisher publisher{0};
|
||||
|
||||
std::vector<TopicPublisher> publishers;
|
||||
std::vector<TopicSubscriber> subscribers;
|
||||
};
|
||||
|
||||
struct TreeNode {
|
||||
@@ -64,45 +125,97 @@ class NetworkTablesModel : public Model {
|
||||
std::vector<TreeNode> children;
|
||||
};
|
||||
|
||||
struct ClientPublisher {
|
||||
int64_t uid = -1;
|
||||
std::string topic;
|
||||
};
|
||||
|
||||
struct ClientSubscriber {
|
||||
int64_t uid = -1;
|
||||
std::vector<std::string> topics;
|
||||
std::string topicsStr;
|
||||
SubscriberOptions options;
|
||||
};
|
||||
|
||||
struct Client {
|
||||
std::string id;
|
||||
std::string conn;
|
||||
std::string version;
|
||||
std::vector<ClientPublisher> publishers;
|
||||
std::vector<ClientSubscriber> subscribers;
|
||||
|
||||
void UpdatePublishers(std::span<const uint8_t> data);
|
||||
void UpdateSubscribers(std::span<const uint8_t> data);
|
||||
};
|
||||
|
||||
NetworkTablesModel();
|
||||
explicit NetworkTablesModel(NT_Inst inst);
|
||||
~NetworkTablesModel() override;
|
||||
explicit NetworkTablesModel(nt::NetworkTableInstance inst);
|
||||
|
||||
void Update() override;
|
||||
bool Exists() override;
|
||||
|
||||
NT_Inst GetInstance() { return m_inst; }
|
||||
const std::vector<Entry*>& GetEntries() { return m_sortedEntries; }
|
||||
const std::vector<TreeNode>& GetTreeRoot() { return m_root; }
|
||||
nt::NetworkTableInstance GetInstance() { return m_inst; }
|
||||
const std::vector<Entry*>& GetEntries() const { return m_sortedEntries; }
|
||||
const std::vector<TreeNode>& GetTreeRoot() const { return m_root; }
|
||||
const std::vector<TreeNode>& GetPersistentTreeRoot() const {
|
||||
return m_persistentRoot;
|
||||
}
|
||||
const std::vector<TreeNode>& GetRetainedTreeRoot() const {
|
||||
return m_retainedRoot;
|
||||
}
|
||||
const std::vector<TreeNode>& GetTransitoryTreeRoot() const {
|
||||
return m_transitoryRoot;
|
||||
}
|
||||
const std::map<std::string, Client, std::less<>>& GetClients() const {
|
||||
return m_clients;
|
||||
}
|
||||
const Client& GetServer() const { return m_server; }
|
||||
Entry* GetEntry(std::string_view name);
|
||||
Entry* AddEntry(NT_Topic topic);
|
||||
|
||||
private:
|
||||
NT_Inst m_inst;
|
||||
NT_EntryListenerPoller m_poller;
|
||||
wpi::DenseMap<NT_Entry, std::unique_ptr<Entry>> m_entries;
|
||||
void RebuildTree();
|
||||
void RebuildTreeImpl(std::vector<TreeNode>* tree, int category);
|
||||
void UpdateClients(std::span<const uint8_t> data);
|
||||
|
||||
nt::NetworkTableInstance m_inst;
|
||||
NT_MultiSubscriber m_subscriber;
|
||||
nt::TopicListenerPoller m_topicPoller;
|
||||
nt::ValueListenerPoller m_valuePoller;
|
||||
wpi::DenseMap<NT_Topic, std::unique_ptr<Entry>> m_entries;
|
||||
|
||||
// sorted by name
|
||||
std::vector<Entry*> m_sortedEntries;
|
||||
|
||||
std::vector<TreeNode> m_root;
|
||||
std::vector<TreeNode> m_persistentRoot;
|
||||
std::vector<TreeNode> m_retainedRoot;
|
||||
std::vector<TreeNode> m_transitoryRoot;
|
||||
|
||||
std::map<std::string, Client, std::less<>> m_clients;
|
||||
Client m_server;
|
||||
};
|
||||
|
||||
using NetworkTablesFlags = int;
|
||||
|
||||
static constexpr const int kNetworkTablesFlags_PrecisionBitShift = 6;
|
||||
static constexpr const int kNetworkTablesFlags_PrecisionBitShift = 9;
|
||||
|
||||
enum NetworkTablesFlags_ {
|
||||
NetworkTablesFlags_TreeView = 1 << 0,
|
||||
NetworkTablesFlags_ReadOnly = 1 << 1,
|
||||
NetworkTablesFlags_ShowConnections = 1 << 2,
|
||||
NetworkTablesFlags_ShowFlags = 1 << 3,
|
||||
NetworkTablesFlags_ShowTimestamp = 1 << 4,
|
||||
NetworkTablesFlags_CreateNoncanonicalKeys = 1 << 5,
|
||||
NetworkTablesFlags_CombinedView = 1 << 1,
|
||||
NetworkTablesFlags_ReadOnly = 1 << 2,
|
||||
NetworkTablesFlags_ShowSpecial = 1 << 3,
|
||||
NetworkTablesFlags_ShowProperties = 1 << 4,
|
||||
NetworkTablesFlags_ShowTimestamp = 1 << 5,
|
||||
NetworkTablesFlags_ShowServerTimestamp = 1 << 6,
|
||||
NetworkTablesFlags_CreateNoncanonicalKeys = 1 << 7,
|
||||
NetworkTablesFlags_Precision = 0xff << kNetworkTablesFlags_PrecisionBitShift,
|
||||
NetworkTablesFlags_Default = (1 & ~NetworkTablesFlags_ReadOnly &
|
||||
~NetworkTablesFlags_CreateNoncanonicalKeys) |
|
||||
NetworkTablesFlags_Default = NetworkTablesFlags_TreeView |
|
||||
(6 << kNetworkTablesFlags_PrecisionBitShift),
|
||||
};
|
||||
|
||||
void DisplayNetworkTablesInfo(NetworkTablesModel* model);
|
||||
|
||||
void DisplayNetworkTables(
|
||||
NetworkTablesModel* model,
|
||||
NetworkTablesFlags flags = NetworkTablesFlags_Default);
|
||||
@@ -120,9 +233,11 @@ class NetworkTablesFlagsSettings {
|
||||
|
||||
private:
|
||||
bool* m_pTreeView = nullptr;
|
||||
bool* m_pShowConnections = nullptr;
|
||||
bool* m_pShowFlags = nullptr;
|
||||
bool* m_pCombinedView = nullptr;
|
||||
bool* m_pShowSpecial = nullptr;
|
||||
bool* m_pShowProperties = nullptr;
|
||||
bool* m_pShowTimestamp = nullptr;
|
||||
bool* m_pShowServerTimestamp = nullptr;
|
||||
bool* m_pCreateNoncanonicalKeys = nullptr;
|
||||
int* m_pPrecision = nullptr;
|
||||
NetworkTablesFlags m_defaultFlags; // NOLINT
|
||||
|
||||
@@ -1,55 +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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_cpp.h>
|
||||
|
||||
namespace glass {
|
||||
|
||||
class NetworkTablesHelper {
|
||||
public:
|
||||
explicit NetworkTablesHelper(NT_Inst inst);
|
||||
~NetworkTablesHelper();
|
||||
|
||||
NetworkTablesHelper(const NetworkTablesHelper&) = delete;
|
||||
NetworkTablesHelper& operator=(const NetworkTablesHelper&) = delete;
|
||||
|
||||
NT_Inst GetInstance() const { return m_inst; }
|
||||
NT_EntryListenerPoller GetPoller() const { return m_poller; }
|
||||
|
||||
NT_Entry GetEntry(std::string_view name) const {
|
||||
return nt::GetEntry(m_inst, name);
|
||||
}
|
||||
|
||||
static constexpr int kDefaultListenerFlags =
|
||||
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) {
|
||||
return nt::AddPolledEntryListener(m_poller, entry, flags);
|
||||
}
|
||||
|
||||
NT_EntryListener AddListener(std::string_view prefix,
|
||||
unsigned int flags = kDefaultListenerFlags) {
|
||||
return nt::AddPolledEntryListener(m_poller, prefix, flags);
|
||||
}
|
||||
|
||||
std::vector<nt::EntryNotification> PollListener() {
|
||||
bool timedOut = false;
|
||||
return nt::PollEntryListener(m_poller, 0, &timedOut);
|
||||
}
|
||||
|
||||
bool IsConnected() const;
|
||||
|
||||
private:
|
||||
NT_Inst m_inst;
|
||||
NT_EntryListenerPoller m_poller;
|
||||
};
|
||||
|
||||
} // namespace glass
|
||||
@@ -9,13 +9,16 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <ntcore_c.h>
|
||||
#include <ntcore_cpp.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
#include <networktables/Topic.h>
|
||||
#include <networktables/TopicListener.h>
|
||||
#include <networktables/ValueListener.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/StringMap.h>
|
||||
|
||||
#include "glass/Model.h"
|
||||
#include "glass/Provider.h"
|
||||
#include "glass/networktables/NetworkTablesHelper.h"
|
||||
|
||||
namespace glass {
|
||||
|
||||
@@ -23,9 +26,10 @@ class Window;
|
||||
|
||||
namespace detail {
|
||||
struct NTProviderFunctions {
|
||||
using Exists = std::function<bool(NT_Inst inst, const char* path)>;
|
||||
using CreateModel =
|
||||
std::function<std::unique_ptr<Model>(NT_Inst inst, const char* path)>;
|
||||
using Exists =
|
||||
std::function<bool(nt::NetworkTableInstance inst, const char* path)>;
|
||||
using CreateModel = std::function<std::unique_ptr<Model>(
|
||||
nt::NetworkTableInstance inst, const char* path)>;
|
||||
using ViewExists = std::function<bool(Model*, const char* path)>;
|
||||
using CreateView =
|
||||
std::function<std::unique_ptr<View>(Window*, Model*, const char* path)>;
|
||||
@@ -41,14 +45,14 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
|
||||
using Provider::CreateViewFunc;
|
||||
|
||||
explicit NetworkTablesProvider(Storage& storage);
|
||||
NetworkTablesProvider(Storage& storage, NT_Inst inst);
|
||||
NetworkTablesProvider(Storage& storage, nt::NetworkTableInstance inst);
|
||||
|
||||
/**
|
||||
* Get the NetworkTables instance being used for this provider.
|
||||
*
|
||||
* @return NetworkTables instance
|
||||
*/
|
||||
NT_Inst GetInstance() const { return m_nt.GetInstance(); }
|
||||
nt::NetworkTableInstance GetInstance() const { return m_inst; }
|
||||
|
||||
/**
|
||||
* Perform global initialization. This should be called prior to
|
||||
@@ -74,8 +78,10 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
|
||||
private:
|
||||
void Update() override;
|
||||
|
||||
NetworkTablesHelper m_nt;
|
||||
NT_EntryListener m_listener{0};
|
||||
nt::NetworkTableInstance m_inst;
|
||||
nt::TopicListenerPoller m_topicPoller;
|
||||
NT_TopicListener m_topicListener{0};
|
||||
nt::ValueListenerPoller m_valuePoller;
|
||||
|
||||
// cached mapping from table name to type string
|
||||
Storage& m_typeCache;
|
||||
@@ -88,17 +94,25 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
|
||||
// mapping from .type string to model/view creators
|
||||
wpi::StringMap<Builder> m_typeMap;
|
||||
|
||||
struct SubListener {
|
||||
nt::StringSubscriber subscriber;
|
||||
NT_ValueListener listener;
|
||||
};
|
||||
|
||||
// mapping from .type topic to subscriber/listener
|
||||
wpi::DenseMap<NT_Topic, SubListener> m_topicMap;
|
||||
|
||||
struct Entry : public ModelEntry {
|
||||
Entry(NT_Entry typeEntry, std::string_view name, const Builder& builder)
|
||||
: ModelEntry{name, [](NT_Inst, const char*) { return true; },
|
||||
Entry(nt::Topic typeTopic, std::string_view name, const Builder& builder)
|
||||
: ModelEntry{name, [](auto, const char*) { return true; },
|
||||
builder.createModel},
|
||||
typeEntry{typeEntry} {}
|
||||
NT_Entry typeEntry;
|
||||
typeTopic{typeTopic} {}
|
||||
nt::Topic typeTopic;
|
||||
};
|
||||
|
||||
void Show(ViewEntry* entry, Window* window) override;
|
||||
|
||||
ViewEntry* GetOrCreateView(const Builder& builder, NT_Entry typeEntry,
|
||||
ViewEntry* GetOrCreateView(const Builder& builder, nt::Topic typeTopic,
|
||||
std::string_view name);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class Storage;
|
||||
|
||||
class NetworkTablesSettings {
|
||||
public:
|
||||
explicit NetworkTablesSettings(Storage& storage,
|
||||
explicit NetworkTablesSettings(std::string_view clientName, Storage& storage,
|
||||
NT_Inst inst = nt::GetDefaultInstance());
|
||||
|
||||
/**
|
||||
@@ -37,9 +37,10 @@ class NetworkTablesSettings {
|
||||
bool m_restart = true;
|
||||
bool m_serverOption = true;
|
||||
EnumSetting m_mode;
|
||||
std::string& m_iniName;
|
||||
std::string& m_persistentFilename;
|
||||
std::string& m_serverTeam;
|
||||
std::string& m_listenAddress;
|
||||
std::string& m_clientName;
|
||||
bool& m_dsClient;
|
||||
|
||||
class Thread : public wpi::SafeThread {
|
||||
@@ -54,6 +55,7 @@ class NetworkTablesSettings {
|
||||
std::string m_iniName;
|
||||
std::string m_serverTeam;
|
||||
std::string m_listenAddress;
|
||||
std::string m_clientName;
|
||||
bool m_dsClient;
|
||||
};
|
||||
wpi::SafeThreadOwner<Thread> m_thread;
|
||||
|
||||
@@ -23,5 +23,5 @@ add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
|
||||
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
|
||||
EXCLUDE_FROM_ALL)
|
||||
|
||||
target_compile_features(gtest PUBLIC cxx_std_17)
|
||||
target_compile_features(gtest_main PUBLIC cxx_std_17)
|
||||
target_compile_features(gtest PUBLIC cxx_std_20)
|
||||
target_compile_features(gtest_main PUBLIC cxx_std_20)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user