Compare commits

...

28 Commits

Author SHA1 Message Date
Peter Johnson
10ed4b3969 [ntcore] Various NT4 fixes (#4474)
* TopicListener: Fix Add() return values
* Update PubSubOption poll storage documentation
* Update NetworkTableEntry::GetValue() doc
* Add documentation regarding asynchronous callbacks
* Unpublish entry: set publisher to nullptr
* Implement ValueListenerPoller default constructor
* Remove SetNetworkIdentity, make parameter to StartClient
* URI-escape client ID, improve error message
* Add connected message with client id; also improve disconnected message a bit
* Handle SetServers either before or after StartClient
* Fix client use-after-free; also delay reconnect after disconnect to rate limit
* Don't re-announce to already subscribed client; we especially don't want to send the last value again
* Always accept in-order sets, only use timestamp for tiebreak
* Fix LocalStorage::StartNetwork race
* Remove unused/unimplemented function

Also:
* [glass] Remove debug print
* [glass] Fix mpack string decoding
* [cameraserver] Fix up startclient
2022-10-21 22:04:14 -07:00
Thad House
4a401b89d7 [hal, wpilib] New DS thread model and implementation (#3787)
The current DS thread model has some pretty major issues. It makes it difficult to know if all data is from the same remote packet, and if the data changes while the robot loop is running. Additionally, the DS thread is used for a few other things (MotorSafety and State Tracking for EducationalRobot). This also makes sim difficult, as user code has to wait for the thread to know it has new data.

This change completely rethinks how threading works in the driver station model.

First, the DS HAL system receives a new data callback, either from Netcomm or DriverStationSim. Inside the context of this callback, all the low latency data is read and put into a cache. Doing some investigation on the robot side, this is perfectly safe to do, and also ensures a ds packet will not be parsed before we finish reading the current packet data.

After all data is read, the cache is swapped with a 2nd buffer. This buffer just stores the data, none of the HAL DS calls read from this buffer. An event is then fired, stating there is new data ready to go.

Robot code calls HAL_UpdateDSData(). This swaps the 2nd buffer with a 3rd buffer, which always contains the current data. This data will not be updated until HAL_UpdateDSData is called again. Which solves the state problem.

The high level driver station classes have. an updateData() call, which calls HAL_UpdateDSData, and then update button state variables, then data log and update the NT FMS data table (Java also caches across the JNI boundary here, but that could trivially be removed). An extra event provider is provided, allowing other threads to know when this call has been completed.

IterativeRobotBase calls DS.updateData() at the beginning of each loop, and only once per loop. This means all commands will always have the same state.

All of this means there is no longer a DS thread. Everything happens synchronously. This means Sim and testing is easier, as you can just call DriverStationSim.NotifyNewData(), and then DriverStation.UpdateData(), and you can guarantee that all the DriverStation.*** data is up to date.

As for Motor Safety and Educational Robot State Handling, those can all be handled by their own threads. The Educational Thread only needs to run under EducationalRobot, and MotorSafety will only be started if there is a motor safety object enabled.
2022-10-21 22:01:55 -07:00
Tyler Veness
c195b4fc46 [wpimath] Clean up PoseEstimator nominal dt docs (#4496) 2022-10-21 19:53:58 -07:00
Tyler Veness
8f2e34c6a3 [build] Remove wpilib prefix from CMake flat install (#4492)
For system installs, `DESTDIR=/usr cmake --install buildfolder` installs
libraries to `/usr/lib` with the correct rpath. Example structure:
```
/usr/include/wpimath/frc/controller/LinearQuadraticRegulator.h
/usr/lib/libwpimath.so
```

Users need to provide `-I/usr/include/wpimath` in their projects. This
is an artifact of the install() commands being in the subdirectory CMake
files.

For other locations, `DESTDIR=/opt/wpilib cmake --install buildfolder`
installs libraries to `/opt/wpilib/lib`. Example structure:
```
/opt/wpilib/include/wpimath/frc/controller/LinearQuadraticRegulator.h
/opt/wpilib/lib/libwpimath.so
```
2022-10-21 19:23:56 -07:00
Tyler Veness
150d692df7 [wpimath] Remove unused private PoseEstimator function (#4495) 2022-10-21 19:22:37 -07:00
shueja-personal
3e5bfff1b5 [wpimath] FromFieldRelativeSpeeds: Add ChassisSpeeds overload (#4494) 2022-10-21 18:58:23 -07:00
Starlight220
9c7e66a27d [commands] C++: Add CommandPtr overload for SetDefaultCommand (#4488) 2022-10-21 06:35:58 -07:00
Tyler Veness
0ca274866b [build] Fix CMake system library opt-ins (#4487)
-DUSE_SYSTEM_EIGEN now only removes include paths for Eigen instead of
drake as well.

The USE_VCPKG flags were renamed to USE_SYSTEM since they seem general
enough for that to work (the find_package() commands work the same way
on Arch).

The system libuv CMake build now works with Linux libuv as well.
2022-10-20 19:47:12 -07:00
Starlight220
dc037f8d41 [commands] Remove EndlessCommand (#4483) 2022-10-20 17:24:54 -07:00
Tyler Veness
16cdc741cf [wpimath] Add Pose3d(Pose2d) constructor (#4485) 2022-10-20 17:23:00 -07:00
Tyler Veness
9d5055176d [build] cmake: Allow disabling ntcore build (#4486) 2022-10-20 17:21:31 -07:00
Thad House
d1e66e1296 [build] Compile all java code with inline string concatenation (#4490)
Workaround identified in https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8278540
2022-10-20 17:20:36 -07:00
Tyler Veness
1fc098e696 Enable log macros to work with no args (#4475)
This is enabled by the C++20 __VA_OPT__ feature.
Uses of "{}" format string were updated.
Some warning suppressions were required for older clang versions.
Also improve codegen of wpi::Logger::Log(), frc::ReportError(), and frc::MakeError();
these generate better and less redundant code if they use fmt::string_view for the
format string instead of templating on it.
2022-10-19 10:49:27 -07:00
Peter Johnson
878cc8defb [wpilib] LiveWindow: Add enableAllTelemetry() (#4480) 2022-10-17 14:39:57 -07:00
Thad House
8153911160 [build] Fix MSVC runtime archiver to grab default runtime (#4478) 2022-10-16 15:37:13 -07:00
Tyler Veness
fbdc810887 Upgrade to C++20 (#4239)
* Use explicit this capture required by C++20
* Use C++20 span
* Replace wpi::numbers with std::numbers
* Fix C++20 clang-tidy warning false positive in fmt
* Remove ciso646 include since C++20 removed that header
* Fix global-buffer-overflow asan warnings in ntcore tests
* Add DIOSetProxy constructor to HAL

* Upgrade MSVC compiler to 2022
* Bump native-utils to 2023.2.7 (changes to std=c++20)

Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2022-10-15 16:33:14 -07:00
Thad House
396143004c [ntcore] Add ntcoreffi binary (#4471)
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2022-10-15 01:02:38 -07:00
Peter Johnson
1f45732700 [build] Update to 2023.2.4 native-utils and new dependencies (#4473)
* Disable class-memaccess warning in Eigen
* Shim NiFpga_OpenHostMemoryBuffer
* Don't deploy .debug files in integration tests
2022-10-14 23:36:47 -07:00
Peter Johnson
574cb41c18 [ntcore] Various fixes (#4469)
* Fix C++ Publisher and Subscriber move assignment 
* Fix Publisher comment typo.
* Publish: check that properties is an object.
Print a warning, but still publish, just with empty properties.
Add error print for unassigned type publish.
* Return boolean from SetProperties
* Document exception-throw in Java for invalid JSON.
2022-10-14 20:50:55 -07:00
Thad House
d9d6c425e7 [build] Force Java 11 source compatibility (#4472)
We want to have the option of falling back to a Java 11 runtime, at least for this year.
2022-10-14 19:39:54 -07:00
Thad House
58b6484dbe Switch away from NI interrupt manager to custom implementation (#3705)
* Switch away from NI interrupt manager to custom implementation

* Formatting

* Fix tidy

* Formatting

* Fix loading

* Make interrupt api public

* Add multiple wait api

* Formatting

* Fix build

* Fix review comments

* wpiformat

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-10-13 17:25:54 -07:00
Tyler Veness
ca43fe2798 [wpimath] Use Units conversions in ComputerVisionUtil docs (NFC) (#4464) 2022-10-12 13:54:15 -07:00
Thad House
87a64ccedc [hal] Convert DutyCycle Raw output to be a high time measurement (#4466)
The existing raw time has an issue where it jumps around, as in the FPGA if the frequency is not a multiple or divisor of 25 Mhz it jumps around by 1 every second. While waiting on an FPGA change, update the API to make raw output give nanoseconds rather then a scaled value. This does a longer read cycle to get the correct value, but in the future if a fast FPGA function is added this can be easily changed.
2022-10-12 10:15:09 -07:00
Starlight220
89a3d00297 [commands] Add FinallyDo and HandleInterrupt decorators (#4412) 2022-10-11 09:53:27 -07:00
Starlight220
1497665f96 [commands] Add C++ versions of Java-only decorators (#4457) 2022-10-10 09:00:11 -07:00
Ryan Blue
27b173374e [wpimath] Add minLinearAccel parameter to DifferentialDriveAccelerationLimiter (#4422) 2022-10-10 08:57:37 -07:00
Ryan Blue
2a13dba8ac [wpilib] TrajectoryUtil: Fix ambiguous documentation (NFC) (#4461) 2022-10-10 08:56:40 -07:00
Peter Johnson
77301b126c [ntcore] NetworkTables 4 (#3217) 2022-10-08 10:01:31 -07:00
802 changed files with 39596 additions and 26851 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -5,5 +5,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2023.1.0"
implementation "edu.wpi.first:native-utils:2023.2.7"
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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() {}
/**

View File

@@ -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();

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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

View File

@@ -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) {

View File

@@ -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_

View File

@@ -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) {

View File

@@ -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 {};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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); });

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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; }

View File

@@ -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; });

View File

@@ -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};

View File

@@ -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;

View File

@@ -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

View File

@@ -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()

View File

@@ -24,6 +24,7 @@ includeOtherLibs {
^fmt/
^frc/
^imgui
^networktables/
^ntcore
^wpi/
^wpigui

View File

@@ -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'

View File

@@ -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");
}

View File

@@ -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) { \

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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*) {

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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