Compare commits

...

67 Commits

Author SHA1 Message Date
Peter Johnson
b114006543 [ntcore] Unify listeners (#4536)
This combines all 4 NT listener APIs (topic, value, connection, and
logging) into a single unified listener API.
2022-10-31 21:52:14 -07:00
Peter Johnson
32fbfb7da6 [build] cmake: Install ntcore generated include files (#4540) 2022-10-31 21:45:24 -07:00
Thad House
02465920fb [build] Update native utils to 2023.4.0 (#4539)
This has some decent changes to the toolchain plugin, so allwpilib is a great way to make sure nothing breaks.
2022-10-31 19:17:42 -07:00
David K Turner
3a5a376465 [wpimath] Increase constexpr support in geometry data types (#4231)
This uses std::is_constant_evaluated() to conditionally use the gcem library for constexpr calculations.
2022-10-31 09:17:00 -07:00
Peter Johnson
1c3c86e9f1 [ntcore] Cache GetEntry(name) values (#4531)
These are typically cached at higher levels anyway, but cache at lowest
C++ layer as well for consistency with NT3.
2022-10-27 23:34:58 -07:00
Starlight220
dcda09f90a [command] Rename trigger methods (#4210)
Motivation

Feedback from 2022 showed that the Trigger API is rather confusing, mostly due to the following:
- duplicate Trigger and Button APIs were available; users were confused searching for a nonexistent difference between them.
- the when terminology was ambiguous and unclear whether it refers to the high state or specifically the rising edge.
- the Active terminology didn't unambiguously refer to the high state; it wasn't unintuitive to understand it as "when the binding is active/polled".
- whileHeld vs whenHeld was very confusing, and the difference between them wasn't obvious. The parallel Trigger verbs, whileActiveContinuously and whileActiveOnce are much less confusing.

Solution

Deprecating Button and its binding methods. The rationale for deprecating Button (and not Trigger) is because Button uses terminology that is needlessly more specific and restricting to the button use case, making the use case of arbitrary trigger conditions unintuitive.

After consideration, deprecation of Button's subclasses was decided against:

- NetworkButton (a trigger condition based on a boolean NT entry/topic) is a use case that is not necessarily intuitive for teams to implement themselves, so it is an abstraction that should be provided in the library. A parallel class for the BooleanEvent level, NetworkBooleanEvent, was also added as part of NT4. NT listeners were considered as a alternative solution, but they require attention to thread safety, and aren't interoperable with the EventLoop API.
- JoystickButton/POVButton provide abstractions around HID buttons. The new Trigger-returning factories on the HID classes are an equal (if not more concise) alternative, but there is no reason not to keep them for those who find their use preferable.

At a later date in the deprecation cycle (perhaps for 2024), when Button is removed, these subclasses should be changed to inherit directly from Trigger.

Trigger's bindings are changed to use True/False terminology, as it should be unambiguous. Each binding type has both True and False variants; for brevity, only the True variants are listed here:

- onTrue (replaces whenActive): schedule on rising edge.
- whileTrue (replaces whileActiveOnce): schedule on rising edge, cancel on falling edge.
- toggleOnTrue (replaces toggleWhenActive): on rising edge, schedule if unscheduled and cancel if scheduled.

Two binding types are completely deprecated:

- cancelWhenActive: this is a fairly niche use case which is better described as having the trigger's rising edge (Trigger.rising()) as an end condition for the command (using Command.until()).
- whileActiveContinuously: however common, this relied on the no-op behavior of scheduling an already-scheduled command. The more correct way to repeat the command if it ends before the falling edge is using Command.repeatedly/RepeatCommand or a RunCommand -- the only difference is if the command is interrupted, but that is more likely to result in two commands perpetually canceling each other than achieve the desired behavior. Manually implementing a blindly-scheduling binding like whileActiveContinuously is still possible, though might not be intuitive.

Notes

It was considered to share BooleanEvent's digital signal terminology; however, once it was decided that Trigger should not inherit from BooleanEvent (due to overload incompatibility) the common terminology was not worth the unintuitiveness stemming from users' unfamiliarity with the signal processing terms.
2022-10-27 22:03:28 -07:00
Tyler Veness
66157397c1 [wpilib] Make drive classes follow NWU axes convention (#4079)
All trigonometric functions and vector classes assume North-West-Up axes
convention, so using North-East-Down convention with them is really
error-prone. We've broken something every time we touched the drive
classes.

We originally used North-East-Down to match the joystick convention, but
the volume of long-lived bugs has made this not worth it in retrospect.

The rest of WPILib also uses North-West-Up, so this makes things
consistent.

KilloughDrive was removed since no one uses it.
2022-10-27 21:59:11 -07:00
Peter Johnson
9e22ffbebf [ntcore] Fix null deref in NT3 client (#4530) 2022-10-27 21:56:15 -07:00
Thad House
648ab6115c [wpigui,dlt,glass,ov] Support arm in GUI tools (#4527) 2022-10-26 23:16:23 -07:00
Tyler Veness
8bc3b04f5b [wpimath] Make ComputerVisionUtil use 3D geometry classes (#4528)
Closes #4189.
2022-10-26 22:20:08 -07:00
Peter Johnson
cfb84a6083 [wpilibc] Don't hang waiting for NT server to start (#4524)
This matches Java behavior.
2022-10-26 10:29:56 -07:00
Tyler Veness
02c47726e1 [wpimath] Remove unused odometry instance from DifferentialDrivePoseEstimator test (#4522) 2022-10-25 22:19:44 -07:00
Peter Johnson
b2a0093294 [ci] Revert upgrade of github-pages-deploy-action (#4521)
It broke publishing
2022-10-25 19:20:30 -07:00
Justin
2a98d6b5d7 [wpimath] PIDController: Add getters for position & velocity tolerances (#4458) 2022-10-25 16:10:19 -07:00
Starlight220
9f36301dc8 [ci] Write wpiformat patch to job summary (#4519) 2022-10-25 12:30:43 -07:00
Jordan McMichael
901fc555f4 [wpimath] Position Delta Odometry for Mecanum (#4514) 2022-10-25 12:28:59 -07:00
Jordan McMichael
4170ec6107 [wpimath] Position Delta Odometry for Swerve (#4493) 2022-10-25 12:28:36 -07:00
sciencewhiz
fe400f68c5 [docs] Add wpinet to docs build (#4517)
Excludes libuv and libuv wrappers.
2022-10-25 08:47:28 -07:00
Peter Johnson
794669b346 [ntcore] Revamp listeners (#4511)
- In both C++ and Java, add listener functions to Instance class (same as NT3 provided)
- Add WaitForListenerQueue functions (same as NT3 provided)
- Move Java non-poller implementation to Instance (previously only handled single instance)
- Change C++ listeners to take non-const references for subscribers etc to help avoid footguns from use of temporary objects (also add doc comment)
- Fix Preferences making .type persistent
2022-10-24 23:27:24 -07:00
Peter Johnson
dcfa85a5d5 [ci] Build sanitizers with clang-14 (#4518) 2022-10-24 22:44:20 -07:00
Peter Johnson
15ad855f1d [ntcore] Add UnitTopic<T> (C++ only) (#4497)
This avoids the need for explicit value() calls (as compared to using
DoubleTopic).  The unit name is published as the "unit" property.

Implementation note: the test needs to be in wpilibc because ntcore does
not depend on wpimath.
2022-10-24 20:07:44 -07:00
Thad House
11244a49d9 [wpilib] Add IsConnected function to all gyros (#4465) 2022-10-24 20:04:16 -07:00
Thad House
1d2e8eb153 [build] Update myRobot deployment (#4515) 2022-10-24 20:03:39 -07:00
Thad House
ad53fb19b4 [hal] Use new HMB api for addressable LED (#4479)
Theres is now a built in HMB api, but you have to dlopen it to access it. Moved our existing infrastructure for this to its own class, added the new functions, then updated interrupts and LEDs to use it.
2022-10-24 18:26:07 -07:00
Thad House
ba850bac3b [hal] Add more shutdown checks and motor safety shutdown (#4510) 2022-10-23 21:59:04 -07:00
Peter Johnson
023a5989f8 [ntcore] Fix typo in NetworkServer client connect message (#4512) 2022-10-23 20:45:01 -07:00
sciencewhiz
c970011ccc [docs] Add Doxygen aliases used by Foonathan memory (#4509) 2022-10-23 18:12:24 -07:00
sciencewhiz
07a43c3d9a [readme] Document clang-format version and /wpiformat (#4503) 2022-10-23 15:59:33 -07:00
sciencewhiz
a05b212b04 [ci] Revert changes to wpiformat task from #4501 (#4508) 2022-10-23 15:49:59 -07:00
Starlight220
09faf31b67 [commands] Replace Command HID inheritance with delegation (#4470) 2022-10-23 12:09:44 -07:00
Starlight220
9e1f9c1133 [commands] Add command factories (#4476)
Co-authored-by: oblarg <emichaelbarnett@gmail.com>
2022-10-23 12:08:22 -07:00
Tyler Veness
f19d2b9b84 [ci] Add NUMBER environment variable to comment command commit script (#4507) 2022-10-23 11:34:03 -07:00
Tyler Veness
a28f93863c [ci] Push comment command commit directly to PR (#4506) 2022-10-23 11:17:52 -07:00
Tyler Veness
c9f61669b8 [ci] Fix comment command commit push (#4505) 2022-10-23 10:47:15 -07:00
Tyler Veness
dcce5ad3b3 [ci] Update github-script API usage (#4504) 2022-10-23 10:15:42 -07:00
Thad House
6836e5923d [wpilibc] Restore get duty cycle scale factor (#4502) 2022-10-23 07:05:09 -07:00
Peter Johnson
335188c652 [dlt] Add deselect/select all buttons to download view (#4499) 2022-10-22 22:12:47 -07:00
Peter Johnson
60a29dcb99 [glass] Field2D: Add "hidden" option for objects (#4498) 2022-10-22 22:12:25 -07:00
PJ Reiniger
b55d5b3034 [ci] Update deprecated github actions (#4501) 2022-10-22 21:09:44 -07:00
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
1044 changed files with 52940 additions and 30823 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,12 +27,17 @@ 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
uses: actions/setup-python@v2
- name: Set up Python 3.8 (macOS)
if: runner.os == 'macOS'
uses: actions/setup-python@v4
with:
python-version: 3.8

View File

@@ -20,8 +20,9 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
@@ -42,7 +43,7 @@ jobs:
- name: Build with Gradle
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
- name: Install SSH Client 🔑
uses: webfactory/ssh-agent@v0.4.1
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
- name: Deploy Java 🚀

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 }}"
@@ -39,7 +39,7 @@ jobs:
env:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.artifact-name }}
path: build/allOutputs
@@ -51,29 +51,38 @@ 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:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 11
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
@@ -94,6 +103,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:
@@ -104,10 +116,10 @@ jobs:
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.artifact-name }}
path: build/allOutputs
path: ${{ matrix.outputs }}
build-documentation:
name: "Build - Documentation"
@@ -116,8 +128,9 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9
@@ -129,7 +142,7 @@ jobs:
env:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: Documentation
path: docs/build/outputs
@@ -142,7 +155,7 @@ jobs:
- uses: actions/checkout@v3
with:
repository: wpilibsuite/build-tools
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
path: combiner/products/build/allOutputs
- name: Flatten Artifacts
@@ -151,8 +164,9 @@ jobs:
run: |
cat combiner/products/build/allOutputs/version.txt
test -s combiner/products/build/allOutputs/version.txt
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 11
- name: Combine
if: |
@@ -177,7 +191,7 @@ jobs:
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: Maven
path: ~/releases

View File

@@ -23,7 +23,7 @@ jobs:
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install clang-format
@@ -41,15 +41,23 @@ jobs:
- name: Generate diff
run: git diff HEAD > wpiformat-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: wpiformat fixes
path: wpiformat-fixes.patch
if: ${{ failure() }}
- name: Write to job summary
run: |
echo '```diff' >> $GITHUB_STEP_SUMMARY
cat wpiformat-fixes.patch >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
if: ${{ failure() }}
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
@@ -59,7 +67,7 @@ jobs:
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install clang-tidy
@@ -79,7 +87,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
@@ -102,8 +110,9 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 13
- name: Install libclang-9
run: sudo apt update && sudo apt install -y libclang-cpp9 libclang1-9

View File

@@ -15,40 +15,29 @@ 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 clang-14
- name: Install jinja
run: python -m pip install jinja2
- name: configure
run: mkdir build && cd build && cmake ${{ matrix.cmake-flags }} ..
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
- name: build
working-directory: build

View File

@@ -22,7 +22,7 @@ jobs:
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.9
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Configure committer identity

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

@@ -37,8 +37,7 @@ So you want to contribute your changes back to WPILib. Great! We have a few cont
## Coding Guidelines
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system. We currently use clang-format 14.0 with wpiformat.
WPILib uses modified Google style guides for both C++ and Java, which can be found in the [styleguide repository](https://github.com/wpilibsuite/styleguide). Autoformatters are available for many popular editors at https://github.com/google/styleguide. Running wpiformat is required for all contributions and is enforced by our continuous integration system.
While the library should be fully formatted according to the styles, additional elements of the style guide were not followed when the library was initially created. All new code should follow the guidelines. If you are looking for some easy ramp-up tasks, finding areas that don't follow the style guide and fixing them is very welcome.
When writing math expressions in documentation, use https://www.unicodeit.net/ to convert LaTeX to a Unicode equivalent that's easier to read. Not all expressions will translate (e.g., superscripts of superscripts) so focus on making it readable by someone who isn't familiar with LaTeX. If content on multiple lines needs to be aligned in Doxygen/Javadoc comments (e.g., integration/summation limits, matrices packed with square brackets and superscripts for them), put them in @verbatim/@endverbatim blocks in Doxygen or `<pre>` tags in Javadoc so they render with monospace font.

View File

@@ -90,4 +90,4 @@ The following 3 tasks can be used for deployment:
Deploying any of these to the roboRIO will disable the current startup project until it is redeployed.
From here, ssh into the roboRIO using the `admin` account (`lvuser` will fail to run in many cases). In the admin home directory, a file for each deploy type will exist (`myRobotCpp`, `myRobotCppStatic` and `myRobotJavaRun`). These can be run to start up the corresponding project.
From here, ssh into the roboRIO using the `lvuser` account and run `frcRunRobot.sh` (It's in path).

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
@@ -62,7 +62,7 @@ On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be ab
Clone the WPILib repository and follow the instructions above for installing any required tooling.
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions. We use clang-format 14.
## Building
@@ -137,7 +137,9 @@ make
#### wpiformat
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat -clang 14` on Windows or `python3 -m wpiformat -clang 14` on other platforms.
Once a PR has been submitted, formatting can be run in CI by commenting `/wpiformat` on the PR. A new commit will be pushed with the formatting changes.
#### Java Code Quality Tools

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
@@ -47,6 +45,7 @@ Drake wpimath/src/main/native/thirdparty/drake/
wpimath/src/test/native/cpp/drake/
wpimath/src/test/native/include/drake/
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
GCEM wpimath/src/main/native/thirdparty/gcem/include/
==============================================================================
Google Test License
@@ -1226,3 +1225,20 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
============
GCEM License
============
Copyright 2022 - ktholer (https://github.com/kthohr/gcem)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

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.4.0"
}

View File

@@ -1,13 +1,14 @@
import groovy.transform.CompileStatic
import javax.inject.Inject
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact
import edu.wpi.first.deployutils.deploy.context.DeployContext
import org.gradle.api.Project
import edu.wpi.first.deployutils.ActionWrapper
import edu.wpi.first.deployutils.deploy.target.RemoteTarget
import edu.wpi.first.deployutils.PredicateWrapper
import groovy.transform.CompileStatic;
import javax.inject.Inject;
import java.util.function.Function
import org.gradle.api.Project;
import edu.wpi.first.deployutils.deploy.CommandDeployResult;
import edu.wpi.first.deployutils.deploy.artifact.MavenArtifact;
import edu.wpi.first.deployutils.deploy.context.DeployContext;
import edu.wpi.first.deployutils.deploy.target.RemoteTarget;
import edu.wpi.first.deployutils.PredicateWrapper;
import edu.wpi.first.deployutils.ActionWrapper;
@CompileStatic
public class WPIJREArtifact extends MavenArtifact {
@@ -17,6 +18,18 @@ public class WPIJREArtifact extends MavenArtifact {
return configName;
}
public boolean isCheckJreVersion() {
return checkJreVersion;
}
public void setCheckJreVersion(boolean checkJreVersion) {
this.checkJreVersion = checkJreVersion;
}
private boolean checkJreVersion = true;
private final String artifactLocation = "edu.wpi.first.jdk:roborio-2023:17.0.5u7-1"
@Inject
public WPIJREArtifact(String name, RemoteTarget target) {
super(name, target);
@@ -24,10 +37,10 @@ public class WPIJREArtifact extends MavenArtifact {
this.configName = configName;
Project project = target.getProject();
getConfiguration().set(project.getConfigurations().create(configName));
getDependency().set(project.getDependencies().add(configName, "edu.wpi.first.jdk:roborio-2022:11.0.12u5-1"));
getDependency().set(project.getDependencies().add(configName, artifactLocation));
setOnlyIf(new PredicateWrapper({ DeployContext ctx ->
return jreMissing(ctx) || project.hasProperty("force-redeploy-jre");
return jreMissing(ctx) || jreOutOfDate(ctx) || project.hasProperty("force-redeploy-jre");
}));
getDirectory().set("/tmp");
@@ -35,7 +48,7 @@ public class WPIJREArtifact extends MavenArtifact {
getPostdeploy().add(new ActionWrapper({ DeployContext ctx ->
ctx.getLogger().log("Installing JRE...");
ctx.execute("opkg remove frc2022-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
ctx.execute("opkg remove frc*-openjdk*; opkg install /tmp/frcjre.ipk; rm /tmp/frcjre.ipk");
ctx.getLogger().log("JRE Deployed!");
}));
}
@@ -44,5 +57,21 @@ public class WPIJREArtifact extends MavenArtifact {
return ctx.execute("if [[ -f \"/usr/local/frc/JRE/bin/java\" ]]; then echo OK; else echo MISSING; fi").getResult().contains("MISSING");
}
private boolean jreOutOfDate(DeployContext ctx) {
if (!checkJreVersion) {
return false;
}
String version = getDependency().get().getVersion();
CommandDeployResult cmdResult = ctx.execute("opkg list-installed | grep openjdk");
if (cmdResult.getExitCode() != 0) {
ctx.getLogger().log("JRE not found");
return false;
}
String result = cmdResult.getResult().trim();
ctx.getLogger().log("Searching for JRE " + version);
ctx.getLogger().log("Found JRE " + result);
boolean matches = result.contains(version);
ctx.getLogger().log(matches ? "JRE Is Correct Version" : "JRE is mismatched. Reinstalling");
return !matches;
}
}

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

@@ -180,7 +180,7 @@ model {
components {
examplesMap.each { key, value ->
if (key == "usbviewer") {
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
if (!project.hasProperty('onlylinuxathena')) {
"${key}"(NativeExecutableSpec) {
targetBuildTypes 'debug'
binaries.all {
@@ -189,7 +189,7 @@ model {
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
lib library: 'cscore', linkage: 'shared'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -199,6 +199,9 @@ model {
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
} else {
it.linker.args << '-lX11'
if (it.targetPlatform.name.startsWith('linuxarm')) {
it.linker.args << '-lGL'
}
}
}
sources {

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

@@ -1,6 +1,6 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
if (!project.hasProperty('onlylinuxathena')) {
description = "roboRIO Team Number Setter"
@@ -24,19 +24,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
nativeUtils {
nativeDependencyContainer {
libssh(getNativeDependencyTypeClass('WPIStaticMavenDependency')) {
groupId = "edu.wpi.first.thirdparty.frc2023"
artifactId = "libssh"
headerClassifier = "headers"
sourceClassifier = "sources"
ext = "zip"
version = '0.95-3'
targetPlatforms.addAll(nativeUtils.wpi.platforms.desktopPlatforms)
}
}
}
apply from: "${rootDir}/shared/libssh.gradle"
task generateCppVersion() {
description = 'Generates the wpilib version class'
@@ -107,7 +95,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -124,6 +112,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
it.linker.args << '-framework' << 'Kerberos'
} else {
it.linker.args << '-lX11'
if (it.targetPlatform.name.startsWith('linuxarm')) {
it.linker.args << '-lGL'
}
}
}
}

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

@@ -75,6 +75,20 @@ void Downloader::DisplayRemoteDirSelector() {
m_cv.notify_all();
}
ImGui::SameLine();
if (ImGui::Button("Deselect All")) {
for (auto&& download : m_downloadList) {
download.enabled = false;
}
}
ImGui::SameLine();
if (ImGui::Button("Select All")) {
for (auto&& download : m_downloadList) {
download.enabled = true;
}
}
// Remote directory text box
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);
if (ImGui::InputText("Remote Dir", &m_remoteDir,

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

@@ -4,6 +4,7 @@ plugins {
}
evaluationDependsOn(':wpiutil')
evaluationDependsOn(':wpinet')
evaluationDependsOn(':ntcore')
evaluationDependsOn(':cscore')
evaluationDependsOn(':hal')
@@ -28,6 +29,7 @@ def cppIncludeRoots = []
cppProjectZips.add(project(':hal').cppHeadersZip)
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
cppProjectZips.add(project(':wpinet').cppHeadersZip)
cppProjectZips.add(project(':ntcore').cppHeadersZip)
cppProjectZips.add(project(':cscore').cppHeadersZip)
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
@@ -57,7 +59,6 @@ doxygen {
if (project.hasProperty('docWarningsAsErrors')) {
// C++20 shims
exclude 'wpi/ghc/filesystem.hpp'
exclude 'wpi/span.h'
// Drake
exclude 'drake/common/**'
@@ -110,7 +111,7 @@ doxygen {
// libuv
exclude 'uv.h'
exclude 'uv/**'
exclude 'wpi/uv/**'
exclude 'wpinet/uv/**'
// json
exclude 'wpi/json.h'
@@ -125,6 +126,12 @@ doxygen {
exclude 'units/**'
}
aliases 'effects=\\par <i>Effects:</i>^^',
'notes=\\par <i>Notes:</i>^^',
'requires=\\par <i>Requires:</i>^^',
'requiredbe=\\par <i>Required Behavior:</i>^^',
'concept{2}=<a href=\"md_doc_concepts.html#\1\">\2</a>',
'defaultbe=\\par <i>Default Behavior:</i>^^'
case_sense_names false
extension_mapping 'inc=C++', 'no_extension=C++'
extract_all true
@@ -198,8 +205,10 @@ 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(':wpinet').sourceSets.main.java
source project(':cscore').sourceSets.main.java
source project(':ntcore').sourceSets.main.java
source project(':wpimath').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

@@ -1,6 +1,6 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
if (!project.hasProperty('onlylinuxathena')) {
apply plugin: 'cpp'
apply plugin: 'c'

View File

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

View File

@@ -1,6 +1,6 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
if (!project.hasProperty('onlylinuxathena')) {
description = "A different kind of dashboard"
@@ -100,7 +100,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -128,7 +128,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -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'
@@ -169,14 +169,14 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
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'
@@ -190,6 +190,9 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
} else {
it.linker.args << '-lX11'
if (it.targetPlatform.name.startsWith('linuxarm')) {
it.linker.args << '-lGL'
}
}
}
}

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;
@@ -69,46 +70,41 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
}
static void NtInitialize() {
// update window title when connection status changes
auto inst = nt::GetDefaultInstance();
auto poller = nt::CreateConnectionListenerPoller(inst);
nt::AddPolledConnectionListener(poller, true);
auto poller = nt::CreateListenerPoller(inst);
nt::AddPolledListener(
poller, inst,
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
gui::AddEarlyExecute([poller] {
auto win = gui::GetSystemWindow();
if (!win) {
return;
}
bool timedOut;
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
if (event.connected) {
glfwSetWindowTitle(
win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
for (auto&& event : nt::ReadListenerQueue(poller)) {
if (auto connInfo = event.GetConnectionInfo()) {
// update window title when connection status changes
if ((event.flags & NT_EVENT_CONNECTED) != 0) {
glfwSetWindowTitle(
win, fmt::format("Glass - Connected ({})", connInfo->remote_ip)
.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
}
} else if (auto msg = event.GetLogMessage()) {
const char* level = "";
if (msg->level >= NT_LOG_CRITICAL) {
level = "CRITICAL: ";
} else if (msg->level >= NT_LOG_ERROR) {
level = "ERROR: ";
} else if (msg->level >= NT_LOG_WARNING) {
level = "WARNING: ";
}
gNetworkTablesLog.Append(fmt::format(
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
}
}
});
// handle NetworkTables log messages
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)) {
const char* level = "";
if (msg.level >= NT_LOG_CRITICAL) {
level = "CRITICAL: ";
} else if (msg.level >= NT_LOG_ERROR) {
level = "ERROR: ";
} else if (msg.level >= NT_LOG_WARNING) {
level = "WARNING: ";
}
gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message,
msg.filename, msg.line));
}
});
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Log"),
"NetworkTables Log", glass::Window::kHide);
@@ -132,9 +128,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 +226,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);
@@ -113,7 +113,7 @@ class PopupState {
struct DisplayOptions {
explicit DisplayOptions(const gui::Texture& texture) : texture{texture} {}
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack };
enum Style { kBoxImage = 0, kLine, kLineClosed, kTrack, kHidden };
static constexpr Style kDefaultStyle = kBoxImage;
static constexpr float kDefaultWeight = 4.0f;
@@ -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; }
@@ -547,7 +547,7 @@ ObjectInfo::ObjectInfo(Storage& storage)
DisplayOptions::kDefaultLength.to<float>())},
m_style{storage.GetString("style"),
DisplayOptions::kDefaultStyle,
{"Box/Image", "Line", "Line (Closed)", "Track"}},
{"Box/Image", "Line", "Line (Closed)", "Track", "Hidden"}},
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
m_color{
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
@@ -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;
}
@@ -840,6 +840,8 @@ void PoseFrameData::Draw(ImDrawList* drawList, std::vector<ImVec2>* center,
left->emplace_back(m_corners[4]);
right->emplace_back(m_corners[5]);
break;
case DisplayOptions::kHidden:
break;
}
if (m_displayOptions.arrows) {

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,72 @@ 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_poller{inst} {
m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
}
NTField2DModel::~NTField2DModel() = default;
void NTField2DModel::Update() {
for (auto&& event : m_nt.PollListener()) {
// .name
if (event.entry == m_name) {
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());
for (auto&& event : m_poller.ReadQueue()) {
if (auto info = event.GetTopicInfo()) {
// handle publish/unpublish
auto name = wpi::drop_front(info->name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
auto [it, match] = Find(event.name);
if (event.flags & NT_NOTIFY_DELETE) {
auto [it, match] = Find(info->name);
if (event.flags & nt::EventFlags::kUnpublish) {
if (match) {
m_objects.erase(it);
}
continue;
} else if (event.flags & NT_NOTIFY_NEW) {
} else if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_objects.emplace(
it, std::make_unique<ObjectModel>(event.name, event.entry));
it, std::make_unique<ObjectModel>(
info->name, nt::DoubleArrayTopic{info->topic}));
}
} else if (!match) {
continue;
}
if (event.flags & (NT_NOTIFY_NEW | NT_NOTIFY_UPDATE)) {
(*it)->NTUpdate(*event.value);
} else if (auto valueData = event.GetValueEventData()) {
// update values
// .name
if (valueData->topic == m_nameTopic.GetHandle()) {
if (valueData->value && valueData->value.IsString()) {
m_nameValue = valueData->value.GetString();
}
continue;
}
auto it =
std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
return e->GetTopic().GetHandle() == valueData->topic;
});
if (it != m_objects.end()) {
(*it)->NTUpdate(valueData->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 +182,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 +192,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,17 @@ 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::Event& 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 +52,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 +73,16 @@ 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::Event& 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 +100,7 @@ void NTMechanismGroupImpl::ForEachObject(
}
}
void NTMechanismGroupImpl::NTUpdate(const nt::EntryNotification& event,
void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
std::string_view name) {
if (name.empty()) {
return;
@@ -115,58 +116,76 @@ 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 (!match) {
it = m_objects.emplace(
it, std::make_unique<NTMechanismObjectModel>(
m_inst, fmt::format("{}/{}", m_path, name), name));
match = true;
if (event.GetTopicInfo()) {
if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_objects.emplace(
it, std::make_unique<NTMechanismObjectModel>(
m_inst, fmt::format("{}/{}", m_path, name), name));
match = true;
}
}
}
if (match) {
if ((*it)->NTUpdate(event, childName)) {
m_objects.erase(it);
if (match) {
if ((*it)->NTUpdate(event, childName)) {
m_objects.erase(it);
}
}
} else if (event.GetValueEventData()) {
if (match) {
(*it)->NTUpdate(event, childName);
}
}
}
bool NTMechanismObjectModel::NTUpdate(const nt::EntryNotification& event,
bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
std::string_view childName) {
if (event.entry == m_type) {
if ((event.flags & NT_NOTIFY_DELETE) != 0) {
return true;
if (auto info = event.GetTopicInfo()) {
if (info->topic == m_typeTopic.GetHandle()) {
if (event.flags & nt::EventFlags::kUnpublish) {
return true;
}
} else if (info->topic != m_colorTopic.GetHandle() &&
info->topic != m_weightTopic.GetHandle() &&
info->topic != m_angleTopic.GetHandle() &&
info->topic != m_lengthTopic.GetHandle()) {
m_group.NTUpdate(event, childName);
}
if (event.value && event.value->IsString()) {
m_typeValue = event.value->GetString();
} else if (auto valueData = event.GetValueEventData()) {
if (valueData->topic == m_typeTopic.GetHandle()) {
if (valueData->value && valueData->value.IsString()) {
m_typeValue = valueData->value.GetString();
}
} else if (valueData->topic == m_colorTopic.GetHandle()) {
if (valueData->value && valueData->value.IsString()) {
ConvertColor(valueData->value.GetString(), &m_colorValue);
}
} else if (valueData->topic == m_weightTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDouble()) {
m_weightValue = valueData->value.GetDouble();
}
} else if (valueData->topic == m_angleTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDouble()) {
m_angleValue = units::degree_t{valueData->value.GetDouble()};
}
} else if (valueData->topic == m_lengthTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDouble()) {
m_lengthValue = units::meter_t{valueData->value.GetDouble()};
}
} else {
m_group.NTUpdate(event, childName);
}
} 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 {
m_group.NTUpdate(event, childName);
}
return false;
}
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,85 +193,73 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
m_group.ForEachObject(func);
}
bool NTUpdate(const nt::EntryNotification& event, std::string_view childName);
bool NTUpdate(const nt::Event& 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::Event& 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()};
if (auto info = event.GetTopicInfo()) {
if (info->topic == m_xTopic.GetHandle() ||
info->topic == m_yTopic.GetHandle()) {
if (event.flags & nt::EventFlags::kUnpublish) {
return true;
}
} else {
m_group.NTUpdate(event, childName);
}
} 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()}};
} else if (auto valueData = event.GetValueEventData()) {
if (valueData->topic == m_xTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDouble()) {
m_pos = frc::Translation2d{units::meter_t{valueData->value.GetDouble()},
m_pos.Y()};
}
} else if (valueData->topic == m_yTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDouble()) {
m_pos = frc::Translation2d{
m_pos.X(), units::meter_t{valueData->value.GetDouble()}};
}
} else {
m_group.NTUpdate(event, childName);
}
} else {
m_group.NTUpdate(event, childName);
}
return false;
}
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
: NTMechanism2DModel{nt::GetDefaultInstance(), path} {}
: NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
NTMechanism2DModel::NTMechanism2DModel(NT_Inst inst, std::string_view path)
: m_nt{inst},
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_poller{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_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
}
NTMechanism2DModel::~NTMechanism2DModel() = default;
void NTMechanism2DModel::Update() {
for (auto&& event : m_nt.PollListener()) {
// .name
if (event.entry == m_name) {
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 (arr.size() == 2) {
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
units::meter_t{arr[1]}};
}
}
}
// 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());
for (auto&& event : m_poller.ReadQueue()) {
if (auto info = event.GetTopicInfo()) {
auto name = wpi::drop_front(info->name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
@@ -268,12 +275,11 @@ void NTMechanism2DModel::Update() {
});
bool match = it != m_roots.end() && (*it)->GetName() == name;
if (event.flags & NT_NOTIFY_NEW) {
if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_roots.emplace(
it,
std::make_unique<RootModel>(
m_nt.GetInstance(), fmt::format("{}{}", m_path, name), name));
it, std::make_unique<RootModel>(
m_inst, fmt::format("{}{}", m_path, name), name));
match = true;
}
}
@@ -282,12 +288,38 @@ void NTMechanism2DModel::Update() {
m_roots.erase(it);
}
}
} else if (auto valueData = event.GetValueEventData()) {
// .name
if (valueData->topic == m_nameTopic.GetHandle()) {
if (valueData->value && valueData->value.IsString()) {
m_nameValue = valueData->value.GetString();
}
continue;
}
// dims
if (valueData->topic == m_dimensionsTopic.GetHandle()) {
if (valueData->value && valueData->value.IsDoubleArray()) {
auto arr = valueData->value.GetDoubleArray();
if (arr.size() == 2) {
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
units::meter_t{arr[1]}};
}
}
}
// backgroundColor
if (valueData->topic == m_bgColorTopic.GetHandle()) {
if (valueData->value && valueData->value.IsString()) {
ConvertColor(valueData->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,16 @@
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_poller{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_listener = m_poller.AddListener({{""}}, nt::EventFlags::kTopic);
for (auto&& childIt : m_storage.GetChildren()) {
auto id = childIt.key();
auto typePtr = m_typeCache.FindValue(id);
@@ -41,15 +41,14 @@ 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_poller.RemoveListener(m_listener);
m_listener = 0;
for (auto&& modelEntry : m_modelEntries) {
modelEntry->model.reset();
@@ -100,35 +99,58 @@ void NetworkTablesProvider::DisplayMenu() {
void NetworkTablesProvider::Update() {
Provider::Update();
// add/remove entries from NT changes
for (auto&& event : m_nt.PollListener()) {
// look for .type fields
std::string_view eventName{event.name};
if (!wpi::ends_with(eventName, "/.type") || !event.value ||
!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());
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);
for (auto&& event : m_poller.ReadQueue()) {
if (auto info = event.GetTopicInfo()) {
// add/remove entries from NT changes
// look for .type fields
if (!wpi::ends_with(info->name, "/.type") || info->type != NT_STRING ||
info->type_str != "string") {
continue;
}
} else if (event.flags & NT_NOTIFY_NEW) {
GetOrCreateView(builderIt->second, event.entry, tableName);
if (event.flags & nt::EventFlags::kUnpublish) {
auto it = m_topicMap.find(info->topic);
if (it != m_topicMap.end()) {
m_poller.RemoveListener(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() == info->topic;
});
if (it2 != m_viewEntries.end()) {
m_viewEntries.erase(it2);
}
} else if (event.flags & nt::EventFlags::kPublish) {
// subscribe to it; use a subscriber so we only get string values
SubListener sublistener;
sublistener.subscriber = nt::StringTopic{info->topic}.Subscribe("");
sublistener.listener = m_poller.AddListener(
sublistener.subscriber,
nt::EventFlags::kValueAll | nt::EventFlags::kImmediate);
m_topicMap.try_emplace(info->topic, std::move(sublistener));
}
} else if (auto valueData = event.GetValueEventData()) {
// handle actual .type strings
if (!valueData->value.IsString()) {
continue;
}
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(valueData->value.GetString());
if (builderIt == m_typeMap.end()) {
continue;
}
auto topicName = nt::GetTopicName(valueData->topic);
auto tableName = wpi::drop_back(topicName, 6);
GetOrCreateView(builderIt->second, nt::Topic{valueData->topic},
tableName);
// cache the type
m_typeCache.SetString(tableName, event.value->GetString());
m_typeCache.SetString(tableName, valueData->value.GetString());
}
}
}
@@ -149,7 +171,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 +202,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,12 @@
#include <utility>
#include <vector>
#include <networktables/MultiSubscriber.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableListener.h>
#include <networktables/StringTopic.h>
#include <ntcore_cpp.h>
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Field2D.h"
namespace glass {
@@ -23,7 +26,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 +43,11 @@ 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::NetworkTableListenerPoller m_poller;
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,10 @@
#include <vector>
#include <frc/geometry/Translation2d.h>
#include <ntcore_cpp.h>
#include <networktables/MultiSubscriber.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableListener.h>
#include "glass/networktables/NetworkTablesHelper.h"
#include "glass/other/Mechanism2D.h"
namespace glass {
@@ -24,7 +25,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 +43,13 @@ 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::NetworkTableListenerPoller m_poller;
std::string m_nameValue;
frc::Translation2d m_dimensionsValue;

Some files were not shown because too many files have changed in this diff Show More