Compare commits

...

126 Commits

Author SHA1 Message Date
Peter Johnson
cf8faa9e67 [wpilib] Update values on controllable sendables (#4667) 2022-11-18 23:50:41 -08:00
Peter Johnson
5ec067c1f8 [ntcore] Implement keep duplicates pub/sub flag (#4666)
Also don't save duplicate NT sets to data log (unless publish keep duplicates flag is set).
2022-11-18 23:50:08 -08:00
Peter Johnson
e962fd2916 [ntcore] Allow numeric-compatible value sets (#4620)
Also fix entry publishing behavior to allow numerically compatible set
default publish following a subscribe.
2022-11-18 22:46:24 -08:00
Ryan Blue
88bd67e7de [ci] Update clang repositories to jammy (#4665) 2022-11-18 20:42:55 -08:00
Jordan McMichael
902e8686d3 [wpimath] Rework odometry APIs to improve feature parity (#4645)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-18 20:42:00 -08:00
Thad House
e2d49181da Update to native utils 2023.8.0 (#4664) 2022-11-18 19:01:26 -08:00
Peter Johnson
149bac55b1 [cscore] Add Arducam OV9281 exposure quirk (#4663)
Reports exposure range as 1-5000 but real range is 1-75.
2022-11-18 14:15:30 -08:00
amquake
88f7a3ccb9 [wpimath] Fix Pose relativeTo documentation (#4661) 2022-11-18 14:07:50 -08:00
sciencewhiz
8acce443f0 [examples] Fix swerve examples to use getDistance for turning encoder (#4652) 2022-11-18 14:06:21 -08:00
Peter Johnson
295a1f8f3b [ntcore] Fix WaitForListenerQueue (#4662) 2022-11-18 14:01:52 -08:00
Dustin Spicuzza
388e7a4265 [ntcore] Provide mechanism to reset internals of NT instance (#4653) 2022-11-18 10:21:05 -08:00
Tyler Veness
13aceea8dc [apriltag] Fix FieldDimensions argument order (#4659) 2022-11-17 22:15:55 -08:00
Ryan Blue
c203f3f0a9 [apriltag] Fix documentation for AprilTagFieldLayout (#4657) 2022-11-17 19:40:51 -08:00
Thad House
f54d495c90 Fix non initialized hal functionality during motor safety init (#4658) 2022-11-17 18:54:45 -08:00
Starlight220
e6392a1570 [cmd] Change factories return type to CommandBase (#4655) 2022-11-17 15:44:16 -08:00
PJ Reiniger
53904e7cf4 [apriltag] Split AprilTag functionality to a separate library (#4578)
Add AprilTagFieldLayout JSON file and move class to edu.wpi.first.apriltag.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-11-17 14:29:29 -08:00
Drew Williams
2e88a496c2 [wpimath] Add support for swerve joystick normalization (#4516) 2022-11-17 10:53:49 -08:00
Jordan McMichael
ce4c45df13 [wpimath] Rework function signatures for Pose Estimation / Odometry (#4642)
Forcing the use of SwerveModulePostition[] and wpi::array<SwerveModulePosition, len> in place of variadic function signatures.
2022-11-17 10:36:33 -08:00
sciencewhiz
0401597d3b [readme] Add wpinet to MavenArtifacts.md (#4651)
Fixes #4639
2022-11-17 10:35:27 -08:00
Jordan McMichael
2e5f9e45bb [wpimath] Remove encoder reset comments on Swerve, Mecanum Odometry and Pose Estimation (#4643)
In both of these cases, encoder resets are not required for normal use.
2022-11-16 15:20:05 -08:00
sciencewhiz
e4b5795fc7 [docs] Disable Doxygen for memory to fix search (#4636) 2022-11-14 20:19:44 -08:00
Peter Johnson
03d0ea188c [build] cmake: Add missing wpinet to installed config file (#4637) 2022-11-14 20:19:13 -08:00
Thad House
3082bd236b [build] Move version file to its own source set (#4638) 2022-11-14 20:19:00 -08:00
Thad House
b7ca860417 [build] Use build cache for sign step (#4635)
The signing step does not get passed the username and password to the server, so it will just read from the build cache. Then to make sure the tasks are all updated correctly, use if developerid exists as a property to all sign tasks, so it will see the new variable value, and relink. Additionally only update the archives during signing, which will speed up signing.
2022-11-14 19:23:13 -08:00
Starlight220
64838e6367 [commands] Remove unsafe default command isFinished check (#4411) 2022-11-14 14:28:30 -08:00
Peter Johnson
1269d2b901 [myRobot] Disable spotbugs (#4565) 2022-11-14 14:26:41 -08:00
Tyler Veness
14d8506b72 [wpimath] Fix units docs for LinearSystemId::IdentifyDrivetrainSystem() (#4600) 2022-11-14 14:26:16 -08:00
ohowe
d1d458db2b [wpimath] Constrain Rotation2d range to -pi to pi (#4611)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-14 14:25:15 -08:00
Tyler Veness
f656e99245 [readme] Add links to development build documentation (#4481)
* Add links to development build documentation
* Use GitHub dev docs links in readme badges
2022-11-14 14:23:47 -08:00
Starlight220
6dd937cef7 [commands] Fix Trigger API docs (NFC) (#4599) 2022-11-14 14:21:35 -08:00
Starlight220
49047c85b9 [commands] Report error on C++ CommandPtr use-after-move (#4575) 2022-11-14 14:20:52 -08:00
Tyler Veness
d07267fed1 [ci] Upgrade containers to Ubuntu 22.04 and remove libclang installation (#4633) 2022-11-14 14:20:08 -08:00
Thad House
b53ce1d3f0 [build, wpiutil] Switch macos to universal binaries (#4628) 2022-11-14 10:36:33 -08:00
Ege Akman
5a320c326b [upstream_util, wpiutil] Refactor python scripts (#4614)
Co-authored-by: Sourcery AI <>
Co-authored-by: Vasista Vovveti <vasistavovveti@gmail.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2022-11-13 23:11:54 -08:00
Peter Johnson
c4e526d315 [glass] Fix NT Mechanism2D (#4626) 2022-11-13 23:09:23 -08:00
Ryan Blue
d122e4254f [ci] Run spotlessApply after wpiformat in comment command (#4623) 2022-11-13 14:07:26 -08:00
Peter Johnson
5a1e7ea036 [wpilibj] FieldObject2d: Add null check to close() (#4619) 2022-11-12 16:51:41 -08:00
Peter Johnson
179f569113 [ntcore] Notify locally on SetDefault (#4617)
This is necessary for the simulation GUI to pick up default publishes.
2022-11-12 06:33:10 -08:00
Peter Johnson
b0f6dc199d [wpilibc] ShuffleboardComponent.WithProperties: Update type (#4615)
ShuffleboardComponentBase::m_properties is now a StringMap<nt::Value>.
2022-11-11 15:08:05 -08:00
Tyler Veness
7836f661cd [wpimath] Add missing open curly brace to units/base.h (#4613) 2022-11-11 13:06:42 -08:00
CarloWoolsey
dbcc1de37f [wpimath] Add DifferentialDriveFeedforward classes which wrap LinearPlantInversionFeedforward (#4598)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-11-10 16:54:51 -08:00
Cory
93890c528b [wpimath] Add additional angular acceleration units (#4610)
Added Turns per second squared
Added Revolutions per minute squared
Added Revolutions per minute per second
2022-11-10 16:47:55 -08:00
Tyler Veness
3d8d5936f9 [wpimath] Add macro for disabling units fmt support (#4609) 2022-11-10 14:12:07 -08:00
Tyler Veness
2b04159dec [wpimath] Update units/base.h license header (#4608) 2022-11-10 14:11:44 -08:00
Thad House
2764004fad [wpinet] Fix incorrect jni definitions (#4605)
Also re-enables the check task that would have caught this.
2022-11-10 09:42:02 -08:00
Thad House
85f1bb8f2b [wpiutil] Reenable jni check task (#4606)
New option was added to JNI plugin to allow skipping specific symbols. This let us generally reenable the check task in wpiutil.
2022-11-10 09:33:26 -08:00
ohowe
231ae2c353 [glass] Plot: Fix Y-axis not being saved (#4594) 2022-11-08 21:33:02 -08:00
Tyler Veness
e92b6dd5f9 [wpilib] Fix AprilTagFieldLayout JSON property name typos (#4597) 2022-11-08 13:27:21 -08:00
Thad House
2a8e0e1cc8 Update all dependencies that use grgit (#4596) 2022-11-08 10:20:16 -08:00
Starlight220
7d06e517e9 [commands] Move SelectCommand factory impl to header (#4581)
It's templated, so it needs to be in the header.
2022-11-07 15:49:36 -08:00
Tyler Veness
323524fed6 [wpimath] Remove deprecated units/units.h header (#4572) 2022-11-07 10:18:36 -08:00
Starlight220
d426873ed1 [commands] Add missing PS4 triangle methods (#4576) 2022-11-07 10:15:54 -08:00
PJ Reiniger
5be5869b2f [apriltags] Use map as internal data model (#4577)
This leaves the file format as a list, but internally will transform the collection of tags into a map on de/serialization. The serialization will probably happen once on startup, but the tag lookup can happen 100s of times a second. This honestly probably doesn't make too much of a performance hit since N is small, but this is a simple O(n) -> O(1) change for lookups.
2022-11-07 10:09:06 -08:00
Griffin Della Grotte
b1b4c1e9e7 [wpimath] Fix Pose3d transformBy rotation type (#4545)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-11-07 09:57:33 -08:00
Dustin Spicuzza
a4054d702f [commands] Allow composing two triggers directly (#4580)
For backwards compatibility reasons.
2022-11-07 09:55:28 -08:00
Dustin Spicuzza
0190301e09 [wpilibc] Explicitly mark EventLoop as non-copyable/non-movable (#4579)
It's already not movable because m_bindings isn't copyable, but pybind11
isn't able to detect that
2022-11-07 09:30:03 -08:00
Peter Johnson
9d1ce6a6d9 [ntcore] Catch file open error when saving preferences (#4571) 2022-11-05 21:16:39 -07:00
Peter Johnson
5005e2ca04 [ntcore] Change Java event mask to EnumSet (#4564)
Also convert NetworkTableInstance.getNetworkMode() to return EnumSet.
2022-11-04 20:25:37 -07:00
PJ Reiniger
fa44a07938 [upstream-utils][mpack] Add upstream util for mpack (#4500) 2022-11-04 20:03:49 -07:00
Peter Johnson
4ba16db645 [ntcore] Various fixes and cleanups (#4544)
* NetworkTableInstance: set handle to 0 after destroy
* Fix multiple notifications of local values
* Detect mismatch between handles
* Server: fix setting min period when no topics
* Limit maximum number of subscribers/publishers/listeners
   This helps find resource leaks and prevents them from causing excessive
   slowdowns/crashes.  The limit on each is currently set to 512.
* Don't use std::swap in move operation
2022-11-04 20:01:21 -07:00
Thad House
837415abfd [hal] Fix joysticks either crashing or returning 0 (#4570) 2022-11-04 19:03:11 -07:00
amquake
2c20fd0d09 [wpilib] SingleJointedArmSim: Check angle equals limit on wouldHit (#4567) 2022-11-04 17:14:46 -07:00
Alex Ryker
64a7136e08 [wpimath] SwerveDrivePoseEstimator: Restore comment about encoder reset (#4569) 2022-11-04 15:03:49 -07:00
Brennen Puth
b2b473b24a [wpilib] Add AprilTag and AprilTagFieldLayout (#4421)
This is an API for looking up a Pose3d from a tag id, and includes functionality to load that map from a JSON file.

This also adds JSON support to Pose3d, Rotation3d. Translation3d, and Quaternion.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
Co-authored-by: AMereBagatelle <themerebagatelle@gmail.com>
2022-11-04 09:56:22 -07:00
Thad House
7aab8fa93a [build] Update to Native Utils 2023.6.0 (#4563) 2022-11-03 20:57:04 -07:00
Starlight220
12c2851856 [commands] WrapperCommand: inherit from CommandBase (#4561)
This makes WrapperCommand Sendable.
Only Java had this issue.
2022-11-03 06:27:23 -07:00
Tyler Veness
0da169dd84 [wpimath] Remove template argument from ElevatorFeedforward (#4554) 2022-11-02 22:54:32 -07:00
Tyler Veness
2416827c25 [wpimath] Fix docs for pose estimator local measurement models (#4558) 2022-11-02 22:53:21 -07:00
Michael Jansen
1177a3522e [wpilib] Fix Xbox/PS4 POV sim for port number constructors (#4548) 2022-11-02 22:52:26 -07:00
ohowe
102344e27a [commands] HID classes: Add missing methods, tweak return types (#4557)
- Make return type of getHID reflect the specific class
- Add getX and getY to CommandJoystick
2022-11-02 22:51:53 -07:00
Peter Johnson
1831ef3e19 [wpilib] Fix Shuffleboard SuppliedValueWidget (#4559)
It was creating duplicate publishers.
2022-11-02 22:49:52 -07:00
Starlight220
a9606ce870 [wpilib] Fix Xbox/PS4 POV sim (#4546) 2022-11-02 10:52:15 -07:00
Tyler Veness
6c80d5eab3 [wpimath] Remove unused SymbolExports.h include from units/base.h (#4541) 2022-11-01 17:18:24 -07:00
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
661 changed files with 24845 additions and 11537 deletions

View File

@@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: ubuntu-22.04
name: Linux
container: wpilib/roborio-cross-ubuntu:2023-22.04
flags: ""
@@ -37,7 +37,7 @@ jobs:
- name: Set up Python 3.8 (macOS)
if: runner.os == 'macOS'
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8

View File

@@ -6,7 +6,7 @@ on:
jobs:
wpiformat:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: React Rocket
uses: actions/github-script@v4
@@ -37,16 +37,23 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 11
- name: Install clang-format
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-format-14
- name: Install wpiformat
run: pip3 install wpiformat
- name: Run wpiformat
run: wpiformat -clang 14
- name: Run spotlessApply
run: ./gradlew spotlessApply
- name: Commit
run: |
# Set credentials

View File

@@ -12,7 +12,7 @@ env:
jobs:
publish:
name: "Documentation - Publish"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
if: github.repository_owner == 'wpilibsuite' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
concurrency: ci-docs-publish
steps:
@@ -20,11 +20,10 @@ 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
- name: Set environment variables (Development)
run: |
echo "TARGET_FOLDER=$BASE_PATH/development" >> $GITHUB_ENV
@@ -42,7 +41,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

@@ -9,7 +9,7 @@ concurrency:
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/gazebo-ubuntu:22.04
steps:
- uses: actions/checkout@v3

View File

@@ -25,7 +25,7 @@ jobs:
artifact-name: Linux
build-options: "-Ponlylinuxx86-64"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@v3
@@ -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
@@ -66,7 +66,6 @@ jobs:
- os: macOS-11
artifact-name: macOS
architecture: x64
build-options: "-Pbuildalldesktop"
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
@@ -80,8 +79,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: 11
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
@@ -111,27 +111,26 @@ jobs:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- name: Sign Libraries with Developer ID
run: ./gradlew build -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
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: ${{ matrix.outputs }}
build-documentation:
name: "Build - Documentation"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- 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
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
@@ -140,7 +139,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
@@ -148,12 +147,12 @@ jobs:
combine:
name: Combine
needs: [build-docker, build-host, build-documentation]
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- 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
@@ -162,8 +161,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: |
@@ -188,7 +188,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

@@ -13,7 +13,7 @@ concurrency:
jobs:
wpiformat:
name: "wpiformat"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -23,13 +23,13 @@ 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
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-format-14
- name: Install wpiformat
@@ -41,14 +41,22 @@ 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
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3
@@ -59,13 +67,13 @@ 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
run: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo sh -c "echo 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo sh -c "echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main' >> /etc/apt/sources.list.d/proposed-repositories.list"
sudo apt-get update -q
sudo apt-get install -y clang-tidy-14 clang-format-14
- name: Install wpiformat
@@ -78,7 +86,7 @@ jobs:
run: wpiformat -clang 14 -no-format -tidy-changed -compile-commands=build/compile_commands/linuxx86-64 -vv
javaformat:
name: "Java format"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/ubuntu-base:22.04
steps:
- uses: actions/checkout@v3
@@ -97,15 +105,14 @@ jobs:
if: ${{ failure() }}
documentation:
name: "Documentation"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- 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
- name: Build with Gradle
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}

View File

@@ -25,19 +25,19 @@ jobs:
ctest-env: ""
ctest-flags: ""
name: "${{ matrix.name }}"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
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

@@ -13,7 +13,7 @@ concurrency:
jobs:
update:
name: "Update"
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -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
@@ -49,6 +49,10 @@ jobs:
run: |
cd upstream_utils
./update_llvm.py
- name: Run update_mpack.py
run: |
cd upstream_utils
./update_mpack.py
- name: Run update_stack_walker.py
run: |
cd upstream_utils

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)
@@ -280,6 +315,7 @@ endif()
if (WITH_WPILIB)
set(WPILIBC_DEP_REPLACE ${WPILIBC_DEP_REPLACE_IMPL})
add_subdirectory(apriltag)
add_subdirectory(wpilibj)
add_subdirectory(wpilibc)
add_subdirectory(wpilibNewCommands)

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

@@ -8,7 +8,7 @@ This article contains instructions on building projects using a development buil
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
In order to build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2023 GradleRIO version, ie `2023.0.0-alpha-1`
```groovy
wpi.maven.useLocal = false
@@ -46,6 +46,11 @@ wpi.versions.wpilibVersion = '2023.+'
wpi.versions.wpimathVersion = '2023.+'
```
### Development Build Documentation
* C++: https://github.wpilib.org/allwpilib/docs/development/cpp/
* Java: https://github.wpilib.org/allwpilib/docs/development/java/
## Local Build
Building with a local build is very similar to building with a development build. Ensure you have built and published WPILib by following the instructions attached [here](https://github.com/wpilibsuite/allwpilib#building-wpilib). Next, find the ``build.gradle`` file in your robot project and open it. Then, add the following code below the plugin section and replace ``YEAR`` with the year of the local version.
@@ -90,4 +95,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

@@ -72,12 +72,16 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* wpigui
* imgui
* ntcore
* wpiutil
* wpimath
* wpiutil
* wpinet
* wpiutil
* ntcore
* wpiutil
* wpinet
* glass/libglass
* wpiutil
* wpimath
@@ -85,6 +89,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* glass/libglassnt
* wpiutil
* wpinet
* ntcore
* wpimath
* wpigui
@@ -94,6 +99,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* halsim
* wpiutil
* wpinet
* ntcore
* wpimath
* wpigui
@@ -102,12 +108,14 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cscore
* opencv
* wpinet
* wpiutil
* cameraserver
* ntcore
* cscore
* opencv
* wpinet
* wpiutil
* wpilibj
@@ -115,6 +123,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* cameraserver
* ntcore
* cscore
* wpinet
* wpiutil
* wpilibc
@@ -123,6 +132,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* ntcore
* cscore
* wpimath
* wpinet
* wpiutil
* wpilibNewCommands
@@ -132,6 +142,7 @@ All artifacts are based at `edu.wpi.first.artifactname` in the repository.
* ntcore
* cscore
* wpimath
* wpinet
* wpiutil

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

@@ -1,8 +1,8 @@
# WPILib Project
![CI](https://github.com/wpilibsuite/allwpilib/workflows/CI/badge.svg)
[![C++ Documentation](https://img.shields.io/badge/documentation-c%2B%2B-blue)](https://first.wpi.edu/wpilib/allwpilib/docs/development/cpp/)
[![Java Documentation](https://img.shields.io/badge/documentation-java-orange)](https://first.wpi.edu/wpilib/allwpilib/docs/development/java/)
[![C++ Documentation](https://img.shields.io/badge/documentation-c%2B%2B-blue)](https://github.wpilib.org/allwpilib/docs/development/cpp/)
[![Java Documentation](https://img.shields.io/badge/documentation-java-orange)](https://github.wpilib.org/allwpilib/docs/development/java/)
Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WPILibC projects. These are the core libraries for creating robot programs for the roboRIO.
@@ -33,7 +33,7 @@ Below is a list of instructions that guide you through cloning, building, publis
1. Clone the repository with `git clone https://github.com/wpilibsuite/allwpilib.git`
2. Build the repository with `./gradlew build` or `./gradlew build --build-cache` if you have an internet connection
3. Publish the artifacts locally by running `./gradlew publish`
4. [Update your](OtherVersions.md) `build.gradle` [to use the artifacts](OtherVersions.md)
4. [Update your](DevelopmentBuilds.md) `build.gradle` [to use the artifacts](DevelopmentBuilds.md)
# Building WPILib
@@ -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
@@ -104,7 +104,7 @@ Run with `--build-cache` on the command-line to use the shared [build cache](htt
### Using Development Builds
Please read the documentation available [here](OtherVersions.md)
Please read the documentation available [here](DevelopmentBuilds.md)
### Custom toolchain location
@@ -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

@@ -45,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
@@ -1224,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.

41
apriltag/CMakeLists.txt Normal file
View File

@@ -0,0 +1,41 @@
project(fieldImages)
include(CompileWarnings)
include(GenResources)
if (WITH_JAVA)
find_package(Java REQUIRED)
include(UseJava)
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
set(CMAKE_JAVA_INCLUDE_PATH apriltags.jar ${JACKSON_JARS})
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
add_jar(apriltags_jar SOURCES ${JAVA_SOURCES} RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES} INCLUDE_JARS wpimath_jar OUTPUT_NAME apriltag)
endif()
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltags_resources_src)
file(GLOB_RECURSE apriltags_native_src src/main/native/cpp/*.cpp)
add_library(apriltags ${apriltags_native_src} ${apriltags_resources_src})
set_target_properties(apriltags PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET apriltags PROPERTY FOLDER "libraries")
target_compile_features(apriltags PUBLIC cxx_std_20)
wpilib_target_warnings(apriltags)
target_link_libraries(apriltags wpimath)
target_include_directories(apriltags PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/apriltags>)
if (WITH_TESTS)
wpilib_add_test(apriltags src/test/native/cpp)
target_include_directories(apriltags_test PRIVATE src/test/native/include)
target_link_libraries(apriltags_test apriltags gmock_main)
endif()

77
apriltag/build.gradle Normal file
View File

@@ -0,0 +1,77 @@
apply from: "${rootDir}/shared/resources.gradle"
ext {
nativeName = 'apriltags'
devMain = 'edu.wpi.first.apriltag.DevMain'
def generateTask = createGenerateResourcesTask('main', 'APRILTAGS', 'frc', project)
tasks.withType(CppCompile) {
dependsOn generateTask
}
extraSetup = {
it.sources {
resourcesCpp(CppSourceSet) {
source {
srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib"
include '*.cpp'
}
}
}
}
}
evaluationDependsOn(':wpimath')
apply from: "${rootDir}/shared/javacpp/setupBuild.gradle"
dependencies {
implementation project(':wpimath')
}
sourceSets {
main {
resources {
srcDirs 'src/main/native/resources'
}
}
}
model {
components {}
binaries {
all {
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
return
}
it.cppCompiler.define 'WPILIB_EXPORTS'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
if ((it instanceof NativeExecutableBinarySpec || it instanceof GoogleTestTestSuiteBinarySpec) && it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
nativeUtils.useRequiredLibrary(it, 'ni_link_libraries', 'ni_runtime_libraries')
}
}
}
tasks {
def c = $.components
def found = false
def systemArch = getCurrentArch()
c.each {
if (it in NativeExecutableSpec && it.name == "${nativeName}Dev") {
it.binaries.each {
if (!found) {
def arch = it.targetPlatform.name
if (arch == systemArch) {
def filePath = it.tasks.install.installDirectory.get().toString() + File.separatorChar + 'lib'
found = true
}
}
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
public final class DevMain {
/** Main entry point. */
public static void main(String[] args) {
System.out.println("Hello World!");
}
private DevMain() {}
}

View File

@@ -0,0 +1,5 @@
// 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.
int main() {}

View File

@@ -0,0 +1,47 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import edu.wpi.first.math.geometry.Pose3d;
import java.util.Objects;
@SuppressWarnings("MemberName")
public class AprilTag {
@JsonProperty(value = "ID")
public int ID;
@JsonProperty(value = "pose")
public Pose3d pose;
@SuppressWarnings("ParameterName")
@JsonCreator
public AprilTag(
@JsonProperty(required = true, value = "ID") int ID,
@JsonProperty(required = true, value = "pose") Pose3d pose) {
this.ID = ID;
this.pose = pose;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AprilTag) {
var other = (AprilTag) obj;
return ID == other.ID && pose.equals(other.pose);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(ID, pose);
}
@Override
public String toString() {
return "AprilTag(ID: " + ID + ", pose: " + pose + ")";
}
}

View File

@@ -0,0 +1,236 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation3d;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Class for representing a layout of AprilTags on a field and reading them from a JSON format.
*
* <p>The JSON format contains two top-level objects, "tags" and "field". The "tags" object is a
* list of all AprilTags contained within a layout. Each AprilTag serializes to a JSON object
* containing an ID and a Pose3d. The "field" object is a descriptor of the size of the field in
* meters with "width" and "length" values. This is to account for arbitrary field sizes when
* transforming the poses.
*
* <p>Pose3ds are assumed to be measured from the bottom-left corner of the field, when the blue
* alliance is at the left. By default, Pose3ds will be returned as declared when calling {@link
* AprilTagFieldLayout#getTagPose(int)}. {@link #setOrigin(OriginPosition)} can be used to transform
* the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be correct relative to a
* different coordinate frame.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
public class AprilTagFieldLayout {
public enum OriginPosition {
kBlueAllianceWallRightSide,
kRedAllianceWallRightSide,
}
private final Map<Integer, AprilTag> m_apriltags = new HashMap<>();
@JsonProperty(value = "field")
private FieldDimensions m_fieldDimensions;
private Pose3d m_origin;
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
* @throws IOException If reading from the file fails.
*/
public AprilTagFieldLayout(String path) throws IOException {
this(Path.of(path));
}
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
* @throws IOException If reading from the file fails.
*/
public AprilTagFieldLayout(Path path) throws IOException {
AprilTagFieldLayout layout =
new ObjectMapper().readValue(path.toFile(), AprilTagFieldLayout.class);
m_apriltags.putAll(layout.m_apriltags);
m_fieldDimensions = layout.m_fieldDimensions;
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
}
/**
* Construct a new AprilTagFieldLayout from a list of {@link AprilTag} objects.
*
* @param apriltags List of {@link AprilTag}.
* @param fieldLength Length of the field the layout is representing in meters.
* @param fieldWidth Width of the field the layout is representing in meters.
*/
public AprilTagFieldLayout(List<AprilTag> apriltags, double fieldLength, double fieldWidth) {
this(apriltags, new FieldDimensions(fieldLength, fieldWidth));
}
@JsonCreator
private AprilTagFieldLayout(
@JsonProperty(required = true, value = "tags") List<AprilTag> apriltags,
@JsonProperty(required = true, value = "field") FieldDimensions fieldDimensions) {
// To ensure the underlying semantics don't change with what kind of list is passed in
for (AprilTag tag : apriltags) {
m_apriltags.put(tag.ID, tag);
}
m_fieldDimensions = fieldDimensions;
setOrigin(OriginPosition.kBlueAllianceWallRightSide);
}
/**
* Returns a List of the {@link AprilTag AprilTags} used in this layout.
*
* @return The {@link AprilTag AprilTags} used in this layout.
*/
@JsonProperty("tags")
public List<AprilTag> getTags() {
return new ArrayList<>(m_apriltags.values());
}
/**
* Sets the origin based on a predefined enumeration of coordinate frame origins. The origins are
* calculated from the field dimensions.
*
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
* relative to a predefined coordinate frame.
*
* @param origin The predefined origin
*/
@JsonIgnore
public void setOrigin(OriginPosition origin) {
switch (origin) {
case kBlueAllianceWallRightSide:
setOrigin(new Pose3d());
break;
case kRedAllianceWallRightSide:
setOrigin(
new Pose3d(
new Translation3d(m_fieldDimensions.fieldLength, m_fieldDimensions.fieldWidth, 0),
new Rotation3d(0, 0, Math.PI)));
break;
default:
throw new IllegalArgumentException("Unsupported enum value");
}
}
/**
* Sets the origin for tag pose transformation.
*
* <p>This transforms the Pose3ds returned by {@link #getTagPose(int)} to return the correct pose
* relative to the provided origin.
*
* @param origin The new origin for tag transformations
*/
@JsonIgnore
public void setOrigin(Pose3d origin) {
m_origin = origin;
}
/**
* Gets an AprilTag pose by its ID.
*
* @param ID The ID of the tag.
* @return The pose corresponding to the ID passed in or an empty optional if a tag with that ID
* was not found.
*/
@SuppressWarnings("ParameterName")
public Optional<Pose3d> getTagPose(int ID) {
AprilTag tag = m_apriltags.get(ID);
if (tag == null) {
return Optional.empty();
}
return Optional.of(tag.pose.relativeTo(m_origin));
}
/**
* Serializes a AprilTagFieldLayout to a JSON file.
*
* @param path The path to write to.
* @throws IOException If writing to the file fails.
*/
public void serialize(String path) throws IOException {
serialize(Path.of(path));
}
/**
* Serializes a AprilTagFieldLayout to a JSON file.
*
* @param path The path to write to.
* @throws IOException If writing to the file fails.
*/
public void serialize(Path path) throws IOException {
new ObjectMapper().writeValue(path.toFile(), this);
}
/**
* Deserializes a field layout from a resource within a jar file.
*
* @param resourcePath The absolute path of the resource
* @return The deserialized layout
* @throws IOException If the resource could not be loaded
*/
public static AprilTagFieldLayout loadFromResource(String resourcePath) throws IOException {
try (InputStream stream = AprilTagFieldLayout.class.getResourceAsStream(resourcePath);
InputStreamReader reader = new InputStreamReader(stream)) {
return new ObjectMapper().readerFor(AprilTagFieldLayout.class).readValue(reader);
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AprilTagFieldLayout) {
var other = (AprilTagFieldLayout) obj;
return m_apriltags.equals(other.m_apriltags) && m_origin.equals(other.m_origin);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(m_apriltags, m_origin);
}
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
private static class FieldDimensions {
@SuppressWarnings("MemberName")
@JsonProperty(value = "length")
public double fieldLength;
@SuppressWarnings("MemberName")
@JsonProperty(value = "width")
public double fieldWidth;
@JsonCreator()
FieldDimensions(
@JsonProperty(required = true, value = "length") double fieldLength,
@JsonProperty(required = true, value = "width") double fieldWidth) {
this.fieldLength = fieldLength;
this.fieldWidth = fieldWidth;
}
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
public enum AprilTagFields {
k2022RapidReact("2022-rapidreact.json");
public static final String kBaseResourceDir = "/edu/wpi/first/apriltag/";
/** Alias to the current game. */
public static final AprilTagFields kDefaultField = k2022RapidReact;
public final String m_resourceFile;
AprilTagFields(String resourceFile) {
m_resourceFile = kBaseResourceDir + resourceFile;
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "frc/apriltag/AprilTag.h"
#include <wpi/json.h>
using namespace frc;
bool AprilTag::operator==(const AprilTag& other) const {
return ID == other.ID && pose == other.pose;
}
bool AprilTag::operator!=(const AprilTag& other) const {
return !operator==(other);
}
void frc::to_json(wpi::json& json, const AprilTag& apriltag) {
json = wpi::json{{"ID", apriltag.ID}, {"pose", apriltag.pose}};
}
void frc::from_json(const wpi::json& json, AprilTag& apriltag) {
apriltag.ID = json.at("ID").get<int>();
apriltag.pose = json.at("pose").get<Pose3d>();
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "frc/apriltag/AprilTagFieldLayout.h"
#include <system_error>
#include <units/angle.h>
#include <units/length.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
using namespace frc;
AprilTagFieldLayout::AprilTagFieldLayout(std::string_view path) {
std::error_code error_code;
wpi::raw_fd_istream input{path, error_code};
if (error_code) {
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
}
wpi::json json;
input >> json;
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
m_apriltags[tag.ID] = tag;
}
m_fieldWidth = units::meter_t{json.at("field").at("width").get<double>()};
m_fieldLength = units::meter_t{json.at("field").at("height").get<double>()};
}
AprilTagFieldLayout::AprilTagFieldLayout(std::vector<AprilTag> apriltags,
units::meter_t fieldLength,
units::meter_t fieldWidth)
: m_fieldLength(std::move(fieldLength)),
m_fieldWidth(std::move(fieldWidth)) {
for (const auto& tag : apriltags) {
m_apriltags[tag.ID] = tag;
}
}
void AprilTagFieldLayout::SetOrigin(OriginPosition origin) {
switch (origin) {
case OriginPosition::kBlueAllianceWallRightSide:
SetOrigin(Pose3d{});
break;
case OriginPosition::kRedAllianceWallRightSide:
SetOrigin(Pose3d{Translation3d{m_fieldLength, m_fieldWidth, 0_m},
Rotation3d{0_deg, 0_deg, 180_deg}});
break;
default:
throw std::invalid_argument("Invalid origin");
}
}
void AprilTagFieldLayout::SetOrigin(const Pose3d& origin) {
m_origin = origin;
}
std::optional<frc::Pose3d> AprilTagFieldLayout::GetTagPose(int ID) const {
const auto& it = m_apriltags.find(ID);
if (it == m_apriltags.end()) {
return std::nullopt;
}
return it->second.pose.RelativeTo(m_origin);
}
void AprilTagFieldLayout::Serialize(std::string_view path) {
std::error_code error_code;
wpi::raw_fd_ostream output{path, error_code};
if (error_code) {
throw std::runtime_error(fmt::format("Cannot open file: {}", path));
}
wpi::json json = *this;
output << json;
output.flush();
}
bool AprilTagFieldLayout::operator==(const AprilTagFieldLayout& other) const {
return m_apriltags == other.m_apriltags && m_origin == other.m_origin &&
m_fieldLength == other.m_fieldLength &&
m_fieldWidth == other.m_fieldWidth;
}
bool AprilTagFieldLayout::operator!=(const AprilTagFieldLayout& other) const {
return !operator==(other);
}
void frc::to_json(wpi::json& json, const AprilTagFieldLayout& layout) {
std::vector<AprilTag> tagVector;
tagVector.reserve(layout.m_apriltags.size());
for (const auto& pair : layout.m_apriltags) {
tagVector.push_back(pair.second);
}
json = wpi::json{{"field",
{{"length", layout.m_fieldLength.value()},
{"width", layout.m_fieldWidth.value()}}},
{"tags", tagVector}};
}
void frc::from_json(const wpi::json& json, AprilTagFieldLayout& layout) {
layout.m_apriltags.clear();
for (const auto& tag : json.at("tags").get<std::vector<AprilTag>>()) {
layout.m_apriltags[tag.ID] = tag;
}
layout.m_fieldLength =
units::meter_t{json.at("field").at("length").get<double>()};
layout.m_fieldWidth =
units::meter_t{json.at("field").at("width").get<double>()};
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "frc/apriltag/AprilTagFields.h"
#include <wpi/json.h>
namespace frc {
// C++ generated from resource files
std::string_view GetResource_2022_rapidreact_json();
AprilTagFieldLayout LoadAprilTagLayoutField(AprilTagField field) {
std::string_view fieldString;
switch (field) {
case AprilTagField::k2022RapidReact:
fieldString = GetResource_2022_rapidreact_json();
break;
case AprilTagField::kNumFields:
throw std::invalid_argument("Invalid Field");
}
wpi::json json = wpi::json::parse(fieldString);
return json.get<AprilTagFieldLayout>();
}
} // namespace frc

View File

@@ -0,0 +1,45 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <wpi/SymbolExports.h>
#include "frc/geometry/Pose3d.h"
namespace wpi {
class json;
} // namespace wpi
namespace frc {
struct WPILIB_DLLEXPORT AprilTag {
int ID;
Pose3d pose;
/**
* Checks equality between this AprilTag and another object.
*
* @param other The other object.
* @return Whether the two objects are equal.
*/
bool operator==(const AprilTag& other) const;
/**
* Checks inequality between this AprilTag and another object.
*
* @param other The other object.
* @return Whether the two objects are not equal.
*/
bool operator!=(const AprilTag& other) const;
};
WPILIB_DLLEXPORT
void to_json(wpi::json& json, const AprilTag& apriltag);
WPILIB_DLLEXPORT
void from_json(const wpi::json& json, AprilTag& apriltag);
} // namespace frc

View File

@@ -0,0 +1,139 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <optional>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <units/length.h>
#include <wpi/SymbolExports.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/geometry/Pose3d.h"
namespace wpi {
class json;
} // namespace wpi
namespace frc {
/**
* Class for representing a layout of AprilTags on a field and reading them from
* a JSON format.
*
* The JSON format contains two top-level objects, "tags" and "field".
* The "tags" object is a list of all AprilTags contained within a layout. Each
* AprilTag serializes to a JSON object containing an ID and a Pose3d. The
* "field" object is a descriptor of the size of the field in meters with
* "width" and "length" values. This is to account for arbitrary field sizes
* when transforming the poses.
*
* Pose3ds are assumed to be measured from the bottom-left corner of the field,
* when the blue alliance is at the left. By default, Pose3ds will be returned
* as declared when calling GetTagPose(int).
* SetOrigin(AprilTagFieldLayout::OriginPosition) can be used to transform the
* poses returned by GetTagPose(int) to be correct relative to a different
* coordinate frame.
*/
class WPILIB_DLLEXPORT AprilTagFieldLayout {
public:
enum class OriginPosition {
kBlueAllianceWallRightSide,
kRedAllianceWallRightSide,
};
AprilTagFieldLayout() = default;
/**
* Construct a new AprilTagFieldLayout with values imported from a JSON file.
*
* @param path Path of the JSON file to import from.
*/
explicit AprilTagFieldLayout(std::string_view path);
/**
* Construct a new AprilTagFieldLayout from a vector of AprilTag objects.
*
* @param apriltags Vector of AprilTags.
* @param fieldLength Length of field the layout is representing.
* @param fieldWidth Width of field the layout is representing.
*/
AprilTagFieldLayout(std::vector<AprilTag> apriltags,
units::meter_t fieldLength, units::meter_t fieldWidth);
/**
* Sets the origin based on a predefined enumeration of coordinate frame
* origins. The origins are calculated from the field dimensions.
*
* This transforms the Pose3ds returned by GetTagPose(int) to return the
* correct pose relative to a predefined coordinate frame.
*
* @param origin The predefined origin
*/
void SetOrigin(OriginPosition origin);
/**
* Sets the origin for tag pose transformation.
*
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
* correct pose relative to the provided origin.
*
* @param origin The new origin for tag transformations
*/
void SetOrigin(const Pose3d& origin);
/**
* Gets an AprilTag pose by its ID.
*
* @param ID The ID of the tag.
* @return The pose corresponding to the ID that was passed in or an empty
* optional if a tag with that ID is not found.
*/
std::optional<Pose3d> GetTagPose(int ID) const;
/**
* Serializes an AprilTagFieldLayout to a JSON file.
*
* @param path The path to write the JSON file to.
*/
void Serialize(std::string_view path);
/*
* Checks equality between this AprilTagFieldLayout and another object.
*
* @param other The other object.
* @return Whether the two objects are equal.
*/
bool operator==(const AprilTagFieldLayout& other) const;
/**
* Checks inequality between this AprilTagFieldLayout and another object.
*
* @param other The other object.
* @return Whether the two objects are not equal.
*/
bool operator!=(const AprilTagFieldLayout& other) const;
private:
std::unordered_map<int, AprilTag> m_apriltags;
units::meter_t m_fieldLength;
units::meter_t m_fieldWidth;
Pose3d m_origin;
friend WPILIB_DLLEXPORT void to_json(wpi::json& json,
const AprilTagFieldLayout& layout);
friend WPILIB_DLLEXPORT void from_json(const wpi::json& json,
AprilTagFieldLayout& layout);
};
WPILIB_DLLEXPORT
void to_json(wpi::json& json, const AprilTagFieldLayout& layout);
WPILIB_DLLEXPORT
void from_json(const wpi::json& json, AprilTagFieldLayout& layout);
} // namespace frc

View File

@@ -0,0 +1,31 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <string_view>
#include <wpi/SymbolExports.h>
#include "frc/apriltag/AprilTagFieldLayout.h"
namespace frc {
enum class AprilTagField {
k2022RapidReact,
// This is a placeholder for denoting the last supported field. This should
// always be the last entry in the enum and should not be used by users
kNumFields,
};
/**
* Loads an AprilTagFieldLayout from a predefined field
*
* @param field The predefined field
*/
WPILIB_DLLEXPORT AprilTagFieldLayout
LoadAprilTagLayoutField(AprilTagField field);
} // namespace frc

View File

@@ -0,0 +1,415 @@
{
"tags" : [ {
"ID" : 0,
"pose" : {
"translation" : {
"x" : -0.0035306,
"y" : 7.578928199999999,
"z" : 0.8858503999999999
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 1,
"pose" : {
"translation" : {
"x" : 3.2327088,
"y" : 5.486654,
"z" : 1.7254728
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 2,
"pose" : {
"translation" : {
"x" : 3.067812,
"y" : 5.3305202,
"z" : 1.3762228
},
"rotation" : {
"quaternion" : {
"W" : 0.7071067811865476,
"X" : 0.0,
"Y" : 0.0,
"Z" : -0.7071067811865475
}
}
}
}, {
"ID" : 3,
"pose" : {
"translation" : {
"x" : 0.0039878,
"y" : 5.058536999999999,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 4,
"pose" : {
"translation" : {
"x" : 0.0039878,
"y" : 3.5124898,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 1.0,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.0
}
}
}
}, {
"ID" : 5,
"pose" : {
"translation" : {
"x" : 0.12110719999999998,
"y" : 1.7178274,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 6,
"pose" : {
"translation" : {
"x" : 0.8733027999999999,
"y" : 0.9412985999999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 7,
"pose" : {
"translation" : {
"x" : 1.6150844,
"y" : 0.15725139999999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : 0.9196502204050923,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.39273842708457407
}
}
}
}, {
"ID" : 10,
"pose" : {
"translation" : {
"x" : 16.4627306,
"y" : 0.6506718,
"z" : 0.8858503999999999
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 11,
"pose" : {
"translation" : {
"x" : 13.2350002,
"y" : 2.743454,
"z" : 1.7254728
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 12,
"pose" : {
"translation" : {
"x" : 13.391388000000001,
"y" : 2.8998418,
"z" : 1.3762228
},
"rotation" : {
"quaternion" : {
"W" : 0.7071067811865476,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.7071067811865475
}
}
}
}, {
"ID" : 13,
"pose" : {
"translation" : {
"x" : 16.4552122,
"y" : 3.1755079999999998,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 14,
"pose" : {
"translation" : {
"x" : 16.4552122,
"y" : 4.7171356,
"z" : 0.80645
},
"rotation" : {
"quaternion" : {
"W" : 6.123233995736766E-17,
"X" : 0.0,
"Y" : 0.0,
"Z" : 1.0
}
}
}
}, {
"ID" : 15,
"pose" : {
"translation" : {
"x" : 16.3350194,
"y" : 6.5149729999999995,
"z" : 0.8937752
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 16,
"pose" : {
"translation" : {
"x" : 15.5904946,
"y" : 7.292695599999999,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 17,
"pose" : {
"translation" : {
"x" : 14.847188999999998,
"y" : 8.0691228,
"z" : 0.8906002000000001
},
"rotation" : {
"quaternion" : {
"W" : -0.37298778257580906,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9278362538989199
}
}
}
}, {
"ID" : 40,
"pose" : {
"translation" : {
"x" : 7.874127,
"y" : 4.9131728,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.5446390350150271,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.838670567945424
}
}
}
}, {
"ID" : 41,
"pose" : {
"translation" : {
"x" : 7.4312271999999995,
"y" : 3.759327,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : -0.20791169081775934,
"X" : -0.0,
"Y" : 0.0,
"Z" : 0.9781476007338057
}
}
}
}, {
"ID" : 42,
"pose" : {
"translation" : {
"x" : 8.585073,
"y" : 3.3164272,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.838670567945424,
"X" : 0.0,
"Y" : 0.0,
"Z" : -0.5446390350150271
}
}
}
}, {
"ID" : 43,
"pose" : {
"translation" : {
"x" : 9.0279728,
"y" : 4.470273,
"z" : 0.7032752
},
"rotation" : {
"quaternion" : {
"W" : 0.9781476007338057,
"X" : 0.0,
"Y" : 0.0,
"Z" : 0.20791169081775934
}
}
}
}, {
"ID" : 50,
"pose" : {
"translation" : {
"x" : 7.6790296,
"y" : 4.3261534,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : 0.17729273396782605,
"X" : -0.22744989571511945,
"Y" : 0.04215534644161733,
"Z" : 0.9565859910053995
}
}
}
}, {
"ID" : 51,
"pose" : {
"translation" : {
"x" : 8.0182466,
"y" : 3.5642296,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : -0.5510435465842192,
"X" : -0.19063969497246985,
"Y" : -0.13102303230819815,
"Z" : 0.8017733354717242
}
}
}
}, {
"ID" : 52,
"pose" : {
"translation" : {
"x" : 8.7801704,
"y" : 3.9034466,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : -0.9565859910053994,
"X" : -0.04215534644161739,
"Y" : -0.22744989571511942,
"Z" : 0.17729273396782633
}
}
}
}, {
"ID" : 53,
"pose" : {
"translation" : {
"x" : 8.4409534,
"y" : 4.6653704,
"z" : 2.4177244
},
"rotation" : {
"quaternion" : {
"W" : 0.8017733354717241,
"X" : -0.1310230323081982,
"Y" : 0.19063969497246983,
"Z" : 0.5510435465842194
}
}
}
} ],
"field" : {
"length" : 8.2296,
"width" : 16.4592
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertEquals;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.util.Units;
import java.util.List;
import org.junit.jupiter.api.Test;
class AprilTagPoseSetOriginTest {
@Test
void transformationMatches() {
var layout =
new AprilTagFieldLayout(
List.of(
new AprilTag(1, new Pose3d(new Translation3d(0, 0, 0), new Rotation3d(0, 0, 0))),
new AprilTag(
2,
new Pose3d(
new Translation3d(
Units.feetToMeters(4.0), Units.feetToMeters(4), Units.feetToMeters(4)),
new Rotation3d(0, 0, Units.degreesToRadians(180))))),
Units.feetToMeters(54.0),
Units.feetToMeters(27.0));
layout.setOrigin(AprilTagFieldLayout.OriginPosition.kRedAllianceWallRightSide);
assertEquals(
new Pose3d(
new Translation3d(Units.feetToMeters(54.0), Units.feetToMeters(27.0), 0.0),
new Rotation3d(0.0, 0.0, Units.degreesToRadians(180.0))),
layout.getTagPose(1).orElse(null));
assertEquals(
new Pose3d(
new Translation3d(
Units.feetToMeters(50.0), Units.feetToMeters(23.0), Units.feetToMeters(4)),
new Rotation3d(0.0, 0.0, 0)),
layout.getTagPose(2).orElse(null));
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.util.Units;
import java.util.List;
import org.junit.jupiter.api.Test;
class AprilTagSerializationTest {
@Test
void deserializeMatches() {
var layout =
new AprilTagFieldLayout(
List.of(
new AprilTag(1, new Pose3d(0, 0, 0, new Rotation3d(0, 0, 0))),
new AprilTag(3, new Pose3d(0, 1, 0, new Rotation3d(0, 0, 0)))),
Units.feetToMeters(54.0),
Units.feetToMeters(27.0));
var objectMapper = new ObjectMapper();
var deserialized =
assertDoesNotThrow(
() ->
objectMapper.readValue(
objectMapper.writeValueAsString(layout), AprilTagFieldLayout.class));
assertEquals(layout, deserialized);
}
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.apriltag;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.math.geometry.Pose3d;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.util.Units;
import java.io.IOException;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class LoadConfigTest {
@ParameterizedTest
@EnumSource(AprilTagFields.class)
void testLoad(AprilTagFields field) {
AprilTagFieldLayout layout =
Assertions.assertDoesNotThrow(
() -> AprilTagFieldLayout.loadFromResource(field.m_resourceFile));
assertNotNull(layout);
}
@Test
void test2022RapidReact() throws IOException {
AprilTagFieldLayout layout =
AprilTagFieldLayout.loadFromResource(AprilTagFields.k2022RapidReact.m_resourceFile);
// Blue Hangar Truss - Hub
Pose3d expectedPose =
new Pose3d(
Units.inchesToMeters(127.272),
Units.inchesToMeters(216.01),
Units.inchesToMeters(67.932),
new Rotation3d(0, 0, 0));
Optional<Pose3d> maybePose = layout.getTagPose(1);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Blue Terminal Near Station
expectedPose =
new Pose3d(
Units.inchesToMeters(4.768),
Units.inchesToMeters(67.631),
Units.inchesToMeters(35.063),
new Rotation3d(0, 0, Math.toRadians(46.25)));
maybePose = layout.getTagPose(5);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Upper Hub Blue-Near
expectedPose =
new Pose3d(
Units.inchesToMeters(332.321),
Units.inchesToMeters(183.676),
Units.inchesToMeters(95.186),
new Rotation3d(0, Math.toRadians(26.75), Math.toRadians(69)));
maybePose = layout.getTagPose(53);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());
// Doesn't exist
maybePose = layout.getTagPose(54);
assertFalse(maybePose.isPresent());
}
}

View File

@@ -0,0 +1,27 @@
// 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 <vector>
#include <wpi/json.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/apriltag/AprilTagFieldLayout.h"
#include "frc/geometry/Pose3d.h"
#include "gtest/gtest.h"
using namespace frc;
TEST(AprilTagJsonTest, DeserializeMatches) {
auto layout = AprilTagFieldLayout{
std::vector{
AprilTag{1, Pose3d{}},
AprilTag{3, Pose3d{0_m, 1_m, 0_m, Rotation3d{0_deg, 0_deg, 0_deg}}}},
54_ft, 27_ft};
AprilTagFieldLayout deserialized;
wpi::json json = layout;
EXPECT_NO_THROW(deserialized = json.get<AprilTagFieldLayout>());
EXPECT_EQ(layout, deserialized);
}

View File

@@ -0,0 +1,33 @@
// 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 <vector>
#include <wpi/json.h>
#include "frc/apriltag/AprilTag.h"
#include "frc/apriltag/AprilTagFieldLayout.h"
#include "frc/geometry/Pose3d.h"
#include "gtest/gtest.h"
using namespace frc;
TEST(AprilTagPoseSetOriginTest, TransformationMatches) {
auto layout = AprilTagFieldLayout{
std::vector<AprilTag>{
AprilTag{1,
Pose3d{0_ft, 0_ft, 0_ft, Rotation3d{0_deg, 0_deg, 0_deg}}},
AprilTag{
2, Pose3d{4_ft, 4_ft, 4_ft, Rotation3d{0_deg, 0_deg, 180_deg}}}},
54_ft, 27_ft};
layout.SetOrigin(
AprilTagFieldLayout::OriginPosition::kRedAllianceWallRightSide);
auto mirrorPose =
Pose3d{54_ft, 27_ft, 0_ft, Rotation3d{0_deg, 0_deg, 180_deg}};
EXPECT_EQ(mirrorPose, *layout.GetTagPose(1));
mirrorPose = Pose3d{50_ft, 23_ft, 4_ft, Rotation3d{0_deg, 0_deg, 0_deg}};
EXPECT_EQ(mirrorPose, *layout.GetTagPose(2));
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "frc/apriltag/AprilTagFields.h"
#include "gtest/gtest.h"
namespace frc {
std::vector<AprilTagField> GetAllFields() {
std::vector<AprilTagField> output;
for (int i = 0; i < static_cast<int>(AprilTagField::kNumFields); ++i) {
output.push_back(static_cast<AprilTagField>(i));
}
return output;
}
TEST(AprilTagFieldsTest, TestLoad2022RapidReact) {
AprilTagFieldLayout layout =
LoadAprilTagLayoutField(AprilTagField::k2022RapidReact);
// Blue Hangar Truss - Hub
auto expectedPose =
Pose3d{127.272_in, 216.01_in, 67.932_in, Rotation3d{0_deg, 0_deg, 0_deg}};
auto maybePose = layout.GetTagPose(1);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Blue Terminal Near Station
expectedPose = Pose3d{4.768_in, 67.631_in, 35.063_in,
Rotation3d{0_deg, 0_deg, 46.25_deg}};
maybePose = layout.GetTagPose(5);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Upper Hub Blue-Near
expectedPose = Pose3d{332.321_in, 183.676_in, 95.186_in,
Rotation3d{0_deg, 26.75_deg, 69_deg}};
maybePose = layout.GetTagPose(53);
EXPECT_TRUE(maybePose);
EXPECT_EQ(expectedPose, *maybePose);
// Doesn't exist
maybePose = layout.GetTagPose(54);
EXPECT_FALSE(maybePose);
}
// Test all of the fields in the enum
class AllFieldsFixtureTest : public ::testing::TestWithParam<AprilTagField> {};
TEST_P(AllFieldsFixtureTest, CheckEntireEnum) {
AprilTagField field = GetParam();
EXPECT_NO_THROW(LoadAprilTagLayoutField(field));
}
INSTANTIATE_TEST_SUITE_P(ValuesEnumTestInstTests, AllFieldsFixtureTest,
::testing::ValuesIn(GetAllFields()));
} // namespace frc

View File

@@ -0,0 +1,11 @@
// 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 "gtest/gtest.h"
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}

View File

@@ -13,10 +13,10 @@ buildscript {
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.1.0'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.0'
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2020.2'
id 'edu.wpi.first.NativeUtils' apply false
id 'edu.wpi.first.GradleJni' version '1.0.0'
id 'edu.wpi.first.GradleJni' version '1.1.0'
id 'edu.wpi.first.GradleVsCode'
id 'idea'
id 'visual-studio'
@@ -124,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")
@@ -132,8 +136,10 @@ subprojects {
}
// Sign outputs with Developer ID
if (project.hasProperty("developerID")) {
tasks.withType(AbstractLinkTask) { task ->
tasks.withType(AbstractLinkTask) { task ->
task.inputs.property "HasDeveloperId", project.hasProperty("developerID")
if (project.hasProperty("developerID")) {
// Don't sign any executables because codesign complains
// about relative rpath.
if (!(task instanceof LinkExecutable)) {

View File

@@ -5,5 +5,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2023.2.7"
implementation "edu.wpi.first:native-utils:2023.8.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

@@ -176,7 +176,7 @@ public final class Main {
} else {
System.out.println("Setting up NetworkTables client for team " + team);
ntinst.setServerTeam(team);
ntinst.startClient4();
ntinst.startClient4("multicameraserver");
}
// start cameras

View File

@@ -183,7 +183,7 @@ int main(int argc, char* argv[]) {
ntinst.StartServer();
} else {
fmt::print("Setting up NetworkTables client for team {}\n", team);
ntinst.StartClient4();
ntinst.StartClient4("multicameraserver");
ntinst.SetServerTeam(team);
}

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

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

@@ -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() {
@@ -141,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
}
}
SDEBUG("{}", "Camera Thread exiting");
SDEBUG("Camera Thread exiting");
SetConnected(false);
}
@@ -152,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;
}
@@ -273,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;
}
@@ -295,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;
}
@@ -314,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;
}
@@ -344,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
DeviceSendSettings(req);
}
SDEBUG("{}", "Settings Thread exiting");
SDEBUG("Settings Thread exiting");
}
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {

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

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

@@ -140,6 +140,12 @@ int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
}
return 100;
}
// Arducam OV9281 exposure setting quirk
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 1 && rawProp.maximum == 5000) {
// real range is 1-75
return 100.0 * (rawValue - 1) / (75 - 1);
}
return 100.0 * (rawValue - rawProp.minimum) /
(rawProp.maximum - rawProp.minimum);
}
@@ -159,6 +165,12 @@ int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp,
}
return quirkLifeCamHd3000[ndx];
}
// Arducam OV9281 exposure setting quirk
if (m_ov9281_exposure && rawProp.name == "raw_exposure_absolute" &&
rawProp.minimum == 1 && rawProp.maximum == 5000) {
// real range is 1-75
return 1 + (75 - 1) * (percentValue / 100.0);
}
return rawProp.minimum +
(rawProp.maximum - rawProp.minimum) * (percentValue / 100.0);
}
@@ -454,7 +466,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 +495,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 +505,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 +513,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 +537,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 +548,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 +591,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 +599,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 +611,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 +637,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 +701,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 +720,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 +729,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
}
return false;
}
SDEBUG4("{}", "enabled streaming");
SDEBUG4("enabled streaming");
m_streaming = true;
return true;
}
@@ -735,7 +746,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 +1011,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;
@@ -1385,6 +1396,7 @@ void UsbCameraImpl::SetQuirks() {
std::string_view desc = GetDescription(descbuf);
m_lifecam_exposure = wpi::ends_with(desc, "LifeCam HD-3000") ||
wpi::ends_with(desc, "LifeCam Cinema (TM)");
m_ov9281_exposure = wpi::contains(desc, "OV9281");
m_picamera = wpi::ends_with(desc, "mmal service");
int deviceNum = GetDeviceNum(m_path.c_str());
@@ -1668,7 +1680,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

@@ -161,6 +161,7 @@ class UsbCameraImpl : public SourceImpl {
// Quirks
bool m_lifecam_exposure{false}; // Microsoft LifeCam exposure
bool m_ps3eyecam_exposure{false}; // PS3 Eyecam exposure
bool m_ov9281_exposure{false}; // Arducam OV9281 exposure
bool m_picamera{false}; // Raspberry Pi camera
//

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

@@ -37,6 +37,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

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

@@ -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)
@@ -109,7 +111,7 @@ doxygen {
// libuv
exclude 'uv.h'
exclude 'uv/**'
exclude 'wpi/uv/**'
exclude 'wpinet/uv/**'
// json
exclude 'wpi/json.h'
@@ -124,6 +126,15 @@ doxygen {
exclude 'units/**'
}
//TODO: building memory docs causes search to break
exclude 'wpi/memory/**'
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
@@ -200,6 +211,7 @@ task generateJavaDocs(type: Javadoc) {
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

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

@@ -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
}
@@ -169,7 +169,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
}
@@ -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

@@ -92,6 +92,8 @@ model {
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
inputs.property "HasDeveloperId", project.hasProperty("developerID")
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.

View File

@@ -70,44 +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;
}
for (auto&& event : nt::ReadConnectionListenerQueue(poller)) {
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] {
for (auto&& msg : nt::ReadLoggerQueue(logPoller)) {
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);

View File

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

@@ -53,11 +53,12 @@ struct PlotSeriesRef {
};
class PlotSeries {
explicit PlotSeries(Storage& storage, int yAxis = 0);
explicit PlotSeries(Storage& storage);
public:
PlotSeries(Storage& storage, std::string_view id);
PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
PlotSeries(Storage& storage, DataSource* source);
PlotSeries(Storage& storage, DataSource* source, int yAxis);
const std::string& GetId() const { return m_id; }
@@ -195,7 +196,7 @@ class PlotView : public View {
} // namespace
PlotSeries::PlotSeries(Storage& storage, int yAxis)
PlotSeries::PlotSeries(Storage& storage)
: m_id{storage.GetString("id")},
m_name{storage.GetString("name")},
m_yAxis{storage.GetInt("yAxis", 0)},
@@ -208,12 +209,10 @@ PlotSeries::PlotSeries(Storage& storage, int yAxis)
m_digital{
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {
m_yAxis = yAxis;
}
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
: PlotSeries{storage, 0} {
: PlotSeries{storage} {
m_id = id;
if (DataSource* source = DataSource::Find(id)) {
SetSource(source);
@@ -222,12 +221,17 @@ PlotSeries::PlotSeries(Storage& storage, std::string_view id)
CheckSource();
}
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
: PlotSeries{storage, yAxis} {
PlotSeries::PlotSeries(Storage& storage, DataSource* source)
: PlotSeries{storage} {
SetSource(source);
m_id = source->GetId();
}
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
: PlotSeries{storage, source} {
m_yAxis = yAxis;
}
void PlotSeries::CheckSource() {
if (!m_newValueConn.connected() && !m_sourceCreatedConn.connected()) {
m_source = nullptr;

View File

@@ -117,58 +117,55 @@ NTField2DModel::NTField2DModel(nt::NetworkTableInstance inst,
{{nt::PubSubOption::SendAll(true),
nt::PubSubOption::Periodic(0.05)}}},
m_nameTopic{inst.GetTopic(fmt::format("{}/.name", path))},
m_topicListener{inst},
m_valueListener{inst} {
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
NT_TOPIC_NOTIFY_UNPUBLISH |
NT_TOPIC_NOTIFY_IMMEDIATE);
m_valueListener.Add(m_tableSub,
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
m_poller{inst} {
m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
}
NTField2DModel::~NTField2DModel() = default;
void NTField2DModel::Update() {
// handle publish/unpublish
for (auto&& event : m_topicListener.ReadQueue()) {
auto name = wpi::drop_front(event.info.name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
auto [it, match] = Find(event.info.name);
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
if (match) {
m_objects.erase(it);
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;
}
continue;
} else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
if (!match) {
it = m_objects.emplace(
it, std::make_unique<ObjectModel>(
event.info.name, nt::DoubleArrayTopic{event.info.topic}));
auto [it, match] = Find(info->name);
if (event.flags & nt::EventFlags::kUnpublish) {
if (match) {
m_objects.erase(it);
}
continue;
} else if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_objects.emplace(
it, std::make_unique<ObjectModel>(
info->name, nt::DoubleArrayTopic{info->topic}));
}
} else if (!match) {
continue;
}
} 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;
}
} else if (!match) {
continue;
}
}
// update values
for (auto&& event : m_valueListener.ReadQueue()) {
// .name
if (event.topic == m_nameTopic.GetHandle()) {
if (event.value && event.value.IsString()) {
m_nameValue = event.value.GetString();
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;
}
continue;
}
auto it =
std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) {
return e->GetTopic().GetHandle() == event.topic;
});
if (it != m_objects.end()) {
(*it)->NTUpdate(event.value);
continue;
}
}
}

View File

@@ -41,8 +41,7 @@ class NTMechanismGroupImpl final {
const char* GetName() const { return m_name.c_str(); }
void ForEachObject(wpi::function_ref<void(MechanismObjectModel& model)> func);
void NTUpdate(const nt::TopicNotification& event, std::string_view name);
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
void NTUpdate(const nt::Event& event, std::string_view name);
protected:
nt::NetworkTableInstance m_inst;
@@ -74,8 +73,7 @@ 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::TopicNotification& event, std::string_view name);
void NTUpdate(const nt::ValueNotification& event, std::string_view name);
bool NTUpdate(const nt::Event& event, std::string_view name);
private:
NTMechanismGroupImpl m_group;
@@ -102,7 +100,7 @@ void NTMechanismGroupImpl::ForEachObject(
}
}
void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event,
void NTMechanismGroupImpl::NTUpdate(const nt::Event& event,
std::string_view name) {
if (name.empty()) {
return;
@@ -118,83 +116,69 @@ void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& 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_TOPIC_NOTIFY_PUBLISH) {
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);
}
}
}
void NTMechanismGroupImpl::NTUpdate(const nt::ValueNotification& event,
std::string_view name) {
if (name.empty()) {
return;
}
std::string_view childName;
std::tie(name, childName) = wpi::split(name, '/');
if (childName.empty()) {
return;
}
auto it = std::lower_bound(
m_objects.begin(), m_objects.end(), name,
[](const auto& e, std::string_view name) { return e->GetName() < name; });
if (it != m_objects.end() && (*it)->GetName() == name) {
(*it)->NTUpdate(event, childName);
}
}
bool NTMechanismObjectModel::NTUpdate(const nt::TopicNotification& event,
bool NTMechanismObjectModel::NTUpdate(const nt::Event& event,
std::string_view childName) {
if (event.info.topic == m_typeTopic.GetHandle()) {
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
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);
}
} 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.info.topic != m_colorTopic.GetHandle() &&
event.info.topic != m_weightTopic.GetHandle() &&
event.info.topic != m_angleTopic.GetHandle() &&
event.info.topic != m_lengthTopic.GetHandle()) {
m_group.NTUpdate(event, childName);
}
return false;
}
void NTMechanismObjectModel::NTUpdate(const nt::ValueNotification& event,
std::string_view childName) {
if (event.topic == m_typeTopic.GetHandle()) {
if (event.value && event.value.IsString()) {
m_typeValue = event.value.GetString();
}
} else if (event.topic == m_colorTopic.GetHandle()) {
if (event.value && event.value.IsString()) {
ConvertColor(event.value.GetString(), &m_colorValue);
}
} else if (event.topic == m_weightTopic.GetHandle()) {
if (event.value && event.value.IsDouble()) {
m_weightValue = event.value.GetDouble();
}
} else if (event.topic == m_angleTopic.GetHandle()) {
if (event.value && event.value.IsDouble()) {
m_angleValue = units::degree_t{event.value.GetDouble()};
}
} else if (event.topic == m_lengthTopic.GetHandle()) {
if (event.value && event.value.IsDouble()) {
m_lengthValue = units::meter_t{event.value.GetDouble()};
}
} else {
m_group.NTUpdate(event, childName);
}
}
class NTMechanism2DModel::RootModel final : public MechanismRootModel {
public:
RootModel(nt::NetworkTableInstance inst, std::string_view path,
@@ -209,8 +193,7 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
m_group.ForEachObject(func);
}
bool NTUpdate(const nt::TopicNotification& event, std::string_view childName);
void NTUpdate(const nt::ValueNotification& event, std::string_view childName);
bool NTUpdate(const nt::Event& event, std::string_view childName);
frc::Translation2d GetPosition() const override { return m_pos; };
@@ -221,36 +204,35 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel {
frc::Translation2d m_pos;
};
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::TopicNotification& event,
bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event,
std::string_view childName) {
if (event.info.topic == m_xTopic.GetHandle() ||
event.info.topic == m_yTopic.GetHandle()) {
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
return true;
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 (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;
}
void NTMechanism2DModel::RootModel::NTUpdate(const nt::ValueNotification& event,
std::string_view childName) {
if (event.topic == m_xTopic.GetHandle()) {
if (event.value && event.value.IsDouble()) {
m_pos = frc::Translation2d{units::meter_t{event.value.GetDouble()},
m_pos.Y()};
}
} else if (event.topic == m_yTopic.GetHandle()) {
if (event.value && event.value.IsDouble()) {
m_pos = frc::Translation2d{m_pos.X(),
units::meter_t{event.value.GetDouble()}};
}
} else {
m_group.NTUpdate(event, childName);
}
}
NTMechanism2DModel::NTMechanism2DModel(std::string_view path)
: NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {}
@@ -265,75 +247,88 @@ NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst,
m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))},
m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))},
m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))},
m_topicListener{m_inst},
m_valueListener{m_inst},
m_poller{m_inst},
m_dimensionsValue{1_m, 1_m} {
m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH |
NT_TOPIC_NOTIFY_UNPUBLISH |
NT_TOPIC_NOTIFY_IMMEDIATE);
m_valueListener.Add(m_tableSub,
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
}
NTMechanism2DModel::~NTMechanism2DModel() = default;
void NTMechanism2DModel::Update() {
for (auto&& event : m_topicListener.ReadQueue()) {
auto name = wpi::drop_front(event.info.name, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
std::string_view childName;
std::tie(name, childName) = wpi::split(name, '/');
if (childName.empty()) {
continue;
}
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
[](const auto& e, std::string_view name) {
return e->GetName() < name;
});
bool match = it != m_roots.end() && (*it)->GetName() == name;
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
if (!match) {
it = m_roots.emplace(
it, std::make_unique<RootModel>(
m_inst, fmt::format("{}{}", m_path, name), name));
match = true;
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;
}
}
if (match) {
if ((*it)->NTUpdate(event, childName)) {
m_roots.erase(it);
std::string_view childName;
std::tie(name, childName) = wpi::split(name, '/');
if (childName.empty()) {
continue;
}
}
}
for (auto&& event : m_valueListener.ReadQueue()) {
// .name
if (event.topic == m_nameTopic.GetHandle()) {
if (event.value && event.value.IsString()) {
m_nameValue = event.value.GetString();
}
continue;
}
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
[](const auto& e, std::string_view name) {
return e->GetName() < name;
});
bool match = it != m_roots.end() && (*it)->GetName() == name;
// dims
if (event.topic == m_dimensionsTopic.GetHandle()) {
if (event.value && event.value.IsDoubleArray()) {
auto arr = event.value.GetDoubleArray();
if (arr.size() == 2) {
m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]},
units::meter_t{arr[1]}};
if (event.flags & nt::EventFlags::kPublish) {
if (!match) {
it = m_roots.emplace(
it, std::make_unique<RootModel>(
m_inst, fmt::format("{}{}", m_path, name), name));
match = true;
}
}
}
if (match) {
if ((*it)->NTUpdate(event, childName)) {
m_roots.erase(it);
}
}
} else if (auto valueData = event.GetValueEventData()) {
if (valueData->topic == m_nameTopic.GetHandle()) {
// .name
if (valueData->value && valueData->value.IsString()) {
m_nameValue = valueData->value.GetString();
}
} else if (valueData->topic == m_dimensionsTopic.GetHandle()) {
// dims
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]}};
}
}
} else if (valueData->topic == m_bgColorTopic.GetHandle()) {
// backgroundColor
if (valueData->value && valueData->value.IsString()) {
ConvertColor(valueData->value.GetString(), &m_bgColorValue);
}
} else {
auto fullName = nt::Topic{valueData->topic}.GetName();
auto name = wpi::drop_front(fullName, m_path.size());
if (name.empty() || name[0] == '.') {
continue;
}
std::string_view childName;
std::tie(name, childName) = wpi::split(name, '/');
if (childName.empty()) {
continue;
}
// backgroundColor
if (event.topic == m_bgColorTopic.GetHandle()) {
if (event.value && event.value.IsString()) {
ConvertColor(event.value.GetString(), &m_bgColorValue);
auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name,
[](const auto& e, std::string_view name) {
return e->GetName() < name;
});
if (it != m_roots.end() && (*it)->GetName() == name) {
if ((*it)->NTUpdate(event, childName)) {
m_roots.erase(it);
}
}
}
}
}

View File

@@ -110,15 +110,10 @@ NetworkTablesModel::NetworkTablesModel()
: NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {}
NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst)
: m_inst{inst},
m_subscriber{nt::SubscribeMultiple(inst.GetHandle(), {{"", "$"}})},
m_topicPoller{inst},
m_valuePoller{inst} {
m_topicPoller.Add({{""}},
NT_TOPIC_NOTIFY_IMMEDIATE | NT_TOPIC_NOTIFY_PROPERTIES |
NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_UNPUBLISH);
m_valuePoller.Add(m_subscriber,
NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL);
: m_inst{inst}, m_poller{inst} {
m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic |
nt::EventFlags::kValueAll |
nt::EventFlags::kImmediate);
}
NetworkTablesModel::Entry::~Entry() {
@@ -176,7 +171,6 @@ static void UpdateMsgpackValueSource(NetworkTablesModel::ValueSource* out,
case mpack::mpack_type_str: {
std::string str;
mpack_read_str(&r, &tag, &str);
mpack_done_str(&r);
out->UpdateFromValue(nt::Value::MakeString(std::move(str), time), name,
"");
break;
@@ -427,57 +421,57 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
void NetworkTablesModel::Update() {
bool updateTree = false;
for (auto&& event : m_topicPoller.ReadQueue()) {
auto& entry = m_entries[event.info.topic];
if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
if (!entry) {
entry = std::make_unique<Entry>();
m_sortedEntries.emplace_back(entry.get());
for (auto&& event : m_poller.ReadQueue()) {
if (auto info = event.GetTopicInfo()) {
auto& entry = m_entries[info->topic];
if (event.flags & nt::EventFlags::kPublish) {
if (!entry) {
entry = std::make_unique<Entry>();
m_sortedEntries.emplace_back(entry.get());
updateTree = true;
}
}
if (event.flags & nt::EventFlags::kUnpublish) {
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
entry.get());
// will be removed completely below
if (it != m_sortedEntries.end()) {
*it = nullptr;
}
m_entries.erase(info->topic);
updateTree = true;
continue;
}
if (event.flags & nt::EventFlags::kProperties) {
updateTree = true;
}
}
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(),
entry.get());
// will be removed completely below
if (it != m_sortedEntries.end()) {
*it = nullptr;
if (entry) {
entry->UpdateTopic(std::move(event));
}
m_entries.erase(event.info.topic);
updateTree = true;
continue;
}
if (event.flags & NT_TOPIC_NOTIFY_PROPERTIES) {
updateTree = true;
}
if (entry) {
entry->UpdateTopic(std::move(event));
}
}
for (auto&& event : m_valuePoller.ReadQueue()) {
auto& entry = m_entries[event.topic];
if (entry) {
entry->UpdateFromValue(std::move(event.value), entry->info.name,
entry->info.type_str);
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
entry->info.type_str == "msgpack") {
fmt::print(stderr, "Updating meta-topic {}\n", entry->info.name);
// meta topic handling
if (entry->info.name == "$clients") {
UpdateClients(entry->value.GetRaw());
} else if (entry->info.name == "$serverpub") {
m_server.UpdatePublishers(entry->value.GetRaw());
} else if (entry->info.name == "$serversub") {
m_server.UpdateSubscribers(entry->value.GetRaw());
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdatePublishers(entry->value.GetRaw());
}
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdateSubscribers(entry->value.GetRaw());
} else if (auto valueData = event.GetValueEventData()) {
auto& entry = m_entries[valueData->topic];
if (entry) {
entry->UpdateFromValue(std::move(valueData->value), entry->info.name,
entry->info.type_str);
if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() &&
entry->info.type_str == "msgpack") {
// meta topic handling
if (entry->info.name == "$clients") {
UpdateClients(entry->value.GetRaw());
} else if (entry->info.name == "$serverpub") {
m_server.UpdatePublishers(entry->value.GetRaw());
} else if (entry->info.name == "$serversub") {
m_server.UpdateSubscribers(entry->value.GetRaw());
} else if (wpi::starts_with(entry->info.name, "$clientpub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdatePublishers(entry->value.GetRaw());
}
} else if (wpi::starts_with(entry->info.name, "$clientsub$")) {
auto it = m_clients.find(wpi::drop_front(entry->info.name, 11));
if (it != m_clients.end()) {
it->second.UpdateSubscribers(entry->value.GetRaw());
}
}
}
}

View File

@@ -23,13 +23,10 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
nt::NetworkTableInstance inst)
: Provider{storage.GetChild("windows")},
m_inst{inst},
m_topicPoller{inst},
m_valuePoller{inst},
m_poller{inst},
m_typeCache{storage.GetChild("types")} {
storage.SetCustomApply([this] {
m_topicListener = m_topicPoller.Add({{""}}, NT_TOPIC_NOTIFY_PUBLISH |
NT_TOPIC_NOTIFY_UNPUBLISH |
NT_TOPIC_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);
@@ -51,8 +48,8 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage,
}
});
storage.SetCustomClear([this, &storage] {
m_topicPoller.Remove(m_topicListener);
m_topicListener = 0;
m_poller.RemoveListener(m_listener);
m_listener = 0;
for (auto&& modelEntry : m_modelEntries) {
modelEntry->model.reset();
}
@@ -102,59 +99,60 @@ void NetworkTablesProvider::DisplayMenu() {
void NetworkTablesProvider::Update() {
Provider::Update();
// add/remove entries from NT changes
for (auto&& event : m_topicPoller.ReadQueue()) {
// look for .type fields
if (!wpi::ends_with(event.info.name, "/.type") ||
event.info.type != NT_STRING || event.info.type_str != "string") {
continue;
}
if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) {
auto it = m_topicMap.find(event.info.topic);
if (it != m_topicMap.end()) {
m_valuePoller.Remove(it->second.listener);
m_topicMap.erase(it);
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;
}
auto it2 = std::find_if(
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
return static_cast<Entry*>(elem->modelEntry)
->typeTopic.GetHandle() == event.info.topic;
});
if (it2 != m_viewEntries.end()) {
m_viewEntries.erase(it2);
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 (event.flags & NT_TOPIC_NOTIFY_PUBLISH) {
// subscribe to it
SubListener sublistener;
sublistener.subscriber = nt::StringTopic{event.info.topic}.Subscribe("");
sublistener.listener =
m_valuePoller.Add(sublistener.subscriber,
NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE);
m_topicMap.try_emplace(event.info.topic, std::move(sublistener));
} 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, valueData->value.GetString());
}
}
// handle actual .type strings
for (auto&& event : m_valuePoller.ReadQueue()) {
if (!event.value.IsString()) {
continue;
}
// only handle ones where we have a builder
auto builderIt = m_typeMap.find(event.value.GetString());
if (builderIt == m_typeMap.end()) {
continue;
}
auto topicName = nt::GetTopicName(event.topic);
auto tableName = wpi::drop_back(topicName, 6);
GetOrCreateView(builderIt->second, nt::Topic{event.topic}, tableName);
// cache the type
m_typeCache.SetString(tableName, event.value.GetString());
}
}
void NetworkTablesProvider::Register(std::string_view typeName,

View File

@@ -61,11 +61,10 @@ void NetworkTablesSettings::Thread::Main() {
if (m_mode == 1 || m_mode == 2) {
std::string_view serverTeam{m_serverTeam};
std::optional<unsigned int> team;
nt::SetNetworkIdentity(m_inst, m_clientName);
if (m_mode == 1) {
nt::StartClient4(m_inst);
nt::StartClient4(m_inst, m_clientName);
} else if (m_mode == 2) {
nt::StartClient3(m_inst);
nt::StartClient3(m_inst, m_clientName);
}
if (!wpi::contains(serverTeam, '.') &&
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {

View File

@@ -12,9 +12,8 @@
#include <networktables/MultiSubscriber.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableListener.h>
#include <networktables/StringTopic.h>
#include <networktables/TopicListener.h>
#include <networktables/ValueListener.h>
#include <ntcore_cpp.h>
#include "glass/other/Field2D.h"
@@ -48,8 +47,7 @@ class NTField2DModel : public Field2DModel {
nt::NetworkTableInstance m_inst;
nt::MultiSubscriber m_tableSub;
nt::StringTopic m_nameTopic;
nt::TopicListenerPoller m_topicListener;
nt::ValueListenerPoller m_valueListener;
nt::NetworkTableListenerPoller m_poller;
std::string m_nameValue;
class ObjectModel;

View File

@@ -13,8 +13,7 @@
#include <frc/geometry/Translation2d.h>
#include <networktables/MultiSubscriber.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/TopicListener.h>
#include <networktables/ValueListener.h>
#include <networktables/NetworkTableListener.h>
#include "glass/other/Mechanism2D.h"
@@ -50,8 +49,7 @@ class NTMechanism2DModel : public Mechanism2DModel {
nt::Topic m_nameTopic;
nt::Topic m_dimensionsTopic;
nt::Topic m_bgColorTopic;
nt::TopicListenerPoller m_topicListener;
nt::ValueListenerPoller m_valueListener;
nt::NetworkTableListenerPoller m_poller;
std::string m_nameValue;
frc::Translation2d m_dimensionsValue;

View File

@@ -14,8 +14,7 @@
#include <vector>
#include <networktables/NetworkTableInstance.h>
#include <networktables/TopicListener.h>
#include <networktables/ValueListener.h>
#include <networktables/NetworkTableListener.h>
#include <ntcore_cpp.h>
#include <wpi/DenseMap.h>
#include <wpi/json.h>
@@ -84,8 +83,10 @@ class NetworkTablesModel : public Model {
Entry& operator=(const Entry&) = delete;
~Entry();
void UpdateTopic(nt::TopicNotification&& event) {
UpdateInfo(std::move(event.info));
void UpdateTopic(nt::Event&& event) {
if (std::holds_alternative<nt::TopicInfo>(event.data)) {
UpdateInfo(std::get<nt::TopicInfo>(std::move(event.data)));
}
}
void UpdateInfo(nt::TopicInfo&& info_);
@@ -179,9 +180,7 @@ class NetworkTablesModel : public Model {
void UpdateClients(std::span<const uint8_t> data);
nt::NetworkTableInstance m_inst;
NT_MultiSubscriber m_subscriber;
nt::TopicListenerPoller m_topicPoller;
nt::ValueListenerPoller m_valuePoller;
nt::NetworkTableListenerPoller m_poller;
wpi::DenseMap<NT_Topic, std::unique_ptr<Entry>> m_entries;
// sorted by name

View File

@@ -10,10 +10,9 @@
#include <vector>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableListener.h>
#include <networktables/StringTopic.h>
#include <networktables/Topic.h>
#include <networktables/TopicListener.h>
#include <networktables/ValueListener.h>
#include <wpi/DenseMap.h>
#include <wpi/StringMap.h>
@@ -79,9 +78,8 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
void Update() override;
nt::NetworkTableInstance m_inst;
nt::TopicListenerPoller m_topicPoller;
NT_TopicListener m_topicListener{0};
nt::ValueListenerPoller m_valuePoller;
nt::NetworkTableListenerPoller m_poller;
NT_Listener m_listener{0};
// cached mapping from table name to type string
Storage& m_typeCache;
@@ -96,7 +94,7 @@ class NetworkTablesProvider : private Provider<detail::NTProviderFunctions> {
struct SubListener {
nt::StringSubscriber subscriber;
NT_ValueListener listener;
NT_Listener listener;
};
// mapping from .type topic to subscriber/listener

View File

@@ -0,0 +1,137 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.hal;
import java.nio.ByteBuffer;
public class DriverStationJNI extends JNIWrapper {
public static native void observeUserProgramStarting();
public static native void observeUserProgramDisabled();
public static native void observeUserProgramAutonomous();
public static native void observeUserProgramTeleop();
public static native void observeUserProgramTest();
public static void report(int resource, int instanceNumber) {
report(resource, instanceNumber, 0, "");
}
public static void report(int resource, int instanceNumber, int context) {
report(resource, instanceNumber, context, "");
}
/**
* Report the usage of a resource of interest.
*
* <p>Original signature: <code>uint32_t report(tResourceType, uint8_t, uint8_t, const
* char*)</code>
*
* @param resource one of the values in the tResourceType above (max value 51).
* @param instanceNumber an index that identifies the resource instance.
* @param context an optional additional context number for some cases (such as module number).
* Set to 0 to omit.
* @param feature a string to be included describing features in use on a specific resource.
* Setting the same resource more than once allows you to change the feature string.
* @return TODO
*/
public static native int report(int resource, int instanceNumber, int context, String feature);
public static native int nativeGetControlWord();
@SuppressWarnings("MissingJavadocMethod")
public static void getControlWord(ControlWord controlWord) {
int word = nativeGetControlWord();
controlWord.update(
(word & 1) != 0,
((word >> 1) & 1) != 0,
((word >> 2) & 1) != 0,
((word >> 3) & 1) != 0,
((word >> 4) & 1) != 0,
((word >> 5) & 1) != 0);
}
private static native int nativeGetAllianceStation();
public static final int kRed1AllianceStation = 0;
public static final int kRed2AllianceStation = 1;
public static final int kRed3AllianceStation = 2;
public static final int kBlue1AllianceStation = 3;
public static final int kBlue2AllianceStation = 4;
public static final int kBlue3AllianceStation = 5;
@SuppressWarnings("MissingJavadocMethod")
public static AllianceStationID getAllianceStation() {
switch (nativeGetAllianceStation()) {
case kRed1AllianceStation:
return AllianceStationID.Red1;
case kRed2AllianceStation:
return AllianceStationID.Red2;
case kRed3AllianceStation:
return AllianceStationID.Red3;
case kBlue1AllianceStation:
return AllianceStationID.Blue1;
case kBlue2AllianceStation:
return AllianceStationID.Blue2;
case kBlue3AllianceStation:
return AllianceStationID.Blue3;
default:
return null;
}
}
public static final int kMaxJoystickAxes = 12;
public static final int kMaxJoystickPOVs = 12;
public static final int kMaxJoysticks = 6;
public static native int getJoystickAxes(byte joystickNum, float[] axesArray);
public static native int getJoystickAxesRaw(byte joystickNum, int[] rawAxesArray);
public static native int getJoystickPOVs(byte joystickNum, short[] povsArray);
public static native int getJoystickButtons(byte joystickNum, ByteBuffer count);
public static native void getAllJoystickData(
float[] axesArray, byte[] rawAxesArray, short[] povsArray, long[] buttonsAndMetadata);
public static native int setJoystickOutputs(
byte joystickNum, int outputs, short leftRumble, short rightRumble);
public static native int getJoystickIsXbox(byte joystickNum);
public static native int getJoystickType(byte joystickNum);
public static native String getJoystickName(byte joystickNum);
public static native int getJoystickAxisType(byte joystickNum, byte axis);
public static native double getMatchTime();
public static native int getMatchInfo(MatchInfoData info);
public static native int sendError(
boolean isError,
int errorCode,
boolean isLVCode,
String details,
String location,
String callStack,
boolean printMsg);
public static native int sendConsoleLine(String line);
public static native void refreshDSData();
public static native void provideNewDataEventHandle(int handle);
public static native void removeNewDataEventHandle(int handle);
public static native boolean getOutputsActive();
private DriverStationJNI() {}
}

View File

@@ -4,7 +4,6 @@
package edu.wpi.first.hal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -13,8 +12,6 @@ import java.util.List;
* .
*/
public final class HAL extends JNIWrapper {
public static native void waitForDSData();
public static native boolean initialize(int timeout, int mode);
public static native void shutdown();
@@ -116,15 +113,13 @@ public final class HAL extends JNIWrapper {
}
}
public static native void observeUserProgramStarting();
public static native boolean getBrownedOut();
public static native void observeUserProgramDisabled();
public static native boolean getSystemActive();
public static native void observeUserProgramAutonomous();
public static native int getPortWithModule(byte module, byte channel);
public static native void observeUserProgramTeleop();
public static native void observeUserProgramTest();
public static native int getPort(byte channel);
public static void report(int resource, int instanceNumber) {
report(resource, instanceNumber, 0, "");
@@ -134,114 +129,9 @@ public final class HAL extends JNIWrapper {
report(resource, instanceNumber, context, "");
}
/**
* Report the usage of a resource of interest.
*
* <p>Original signature: <code>uint32_t report(tResourceType, uint8_t, uint8_t, const
* char*)</code>
*
* @param resource one of the values in the tResourceType above (max value 51).
* @param instanceNumber an index that identifies the resource instance.
* @param context an optional additional context number for some cases (such as module number).
* Set to 0 to omit.
* @param feature a string to be included describing features in use on a specific resource.
* Setting the same resource more than once allows you to change the feature string.
* @return TODO
*/
public static native int report(int resource, int instanceNumber, int context, String feature);
public static native int nativeGetControlWord();
/**
* Get the current DriverStation control word.
*
* @param controlWord Storage for control word.
*/
public static void getControlWord(ControlWord controlWord) {
int word = nativeGetControlWord();
controlWord.update(
(word & 1) != 0,
((word >> 1) & 1) != 0,
((word >> 2) & 1) != 0,
((word >> 3) & 1) != 0,
((word >> 4) & 1) != 0,
((word >> 5) & 1) != 0);
public static int report(int resource, int instanceNumber, int context, String feature) {
return DriverStationJNI.report(resource, instanceNumber, context, feature);
}
private static native int nativeGetAllianceStation();
/**
* Get the alliance station.
*
* @return The alliance station.
*/
public static AllianceStationID getAllianceStation() {
switch (nativeGetAllianceStation()) {
case 0:
return AllianceStationID.Red1;
case 1:
return AllianceStationID.Red2;
case 2:
return AllianceStationID.Red3;
case 3:
return AllianceStationID.Blue1;
case 4:
return AllianceStationID.Blue2;
case 5:
return AllianceStationID.Blue3;
default:
return null;
}
}
public static native boolean isNewControlData();
public static native void releaseDSMutex();
public static native boolean waitForDSDataTimeout(double timeout);
public static final int kMaxJoystickAxes = 12;
public static final int kMaxJoystickPOVs = 12;
public static native short getJoystickAxes(byte joystickNum, float[] axesArray);
public static native short getJoystickPOVs(byte joystickNum, short[] povsArray);
public static native int getJoystickButtons(byte joystickNum, ByteBuffer count);
public static native int setJoystickOutputs(
byte joystickNum, int outputs, short leftRumble, short rightRumble);
public static native int getJoystickIsXbox(byte joystickNum);
public static native int getJoystickType(byte joystickNum);
public static native String getJoystickName(byte joystickNum);
public static native int getJoystickAxisType(byte joystickNum, byte axis);
public static native double getMatchTime();
public static native boolean getSystemActive();
public static native boolean getBrownedOut();
public static native int getMatchInfo(MatchInfoData info);
public static native int sendError(
boolean isError,
int errorCode,
boolean isLVCode,
String details,
String location,
String callStack,
boolean printMsg);
public static native int sendConsoleLine(String line);
public static native int getPortWithModule(byte module, byte channel);
public static native int getPort(byte channel);
private HAL() {}
}

View File

@@ -6,11 +6,11 @@
#include <cstring>
#include <FRC_FPGA_ChipObject/fpgainterfacecapi/NiFpga_HMB.h>
#include <fmt/format.h>
#include "ConstantsInternal.h"
#include "DigitalInternal.h"
#include "FPGACalls.h"
#include "HALInitializer.h"
#include "HALInternal.h"
#include "PortsInternal.h"
@@ -21,51 +21,6 @@
using namespace hal;
extern "C" {
NiFpga_Status NiFpga_ClientFunctionCall(NiFpga_Session session, uint32_t group,
uint32_t functionId,
const void* inBuffer,
size_t inBufferSize, void* outBuffer,
size_t outBufferSize);
} // extern "C"
// Shim for broken ChipObject function
static const uint32_t clientFeature_hostMemoryBuffer = 0;
static const uint32_t hostMemoryBufferFunction_open = 2;
// Input arguments for HMB open
struct AtomicHMBOpenInputs {
const char* memoryName;
};
// Output arguments for HMB open
struct AtomicHMBOpenOutputs {
size_t size;
void* virtualAddress;
};
static NiFpga_Status OpenHostMemoryBuffer(NiFpga_Session session,
const char* memoryName,
void** virtualAddress, size_t* size) {
struct AtomicHMBOpenOutputs outputs;
struct AtomicHMBOpenInputs inputs;
inputs.memoryName = memoryName;
NiFpga_Status retval = NiFpga_ClientFunctionCall(
session, clientFeature_hostMemoryBuffer, hostMemoryBufferFunction_open,
&inputs, sizeof(struct AtomicHMBOpenInputs), &outputs,
sizeof(struct AtomicHMBOpenOutputs));
if (NiFpga_IsError(retval)) {
return retval;
}
*virtualAddress = outputs.virtualAddress;
if (size) {
*size = outputs.size;
}
return retval;
}
namespace {
struct AddressableLED {
std::unique_ptr<tLED> led;
@@ -89,6 +44,8 @@ void InitializeAddressableLED() {
}
} // namespace hal::init
static constexpr const char* HmbName = "HMB_0_LED";
extern "C" {
HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
@@ -146,8 +103,8 @@ HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
uint32_t session = led->led->getSystemInterface()->getHandle();
*status = OpenHostMemoryBuffer(session, "HMB_0_LED", &led->ledBuffer,
&led->ledBufferSize);
*status = hal::HAL_NiFpga_OpenHmb(session, HmbName, &led->ledBufferSize,
&led->ledBuffer);
if (*status != 0) {
addressableLEDHandles->Free(handle);
@@ -158,6 +115,12 @@ HAL_AddressableLEDHandle HAL_InitializeAddressableLED(
}
void HAL_FreeAddressableLED(HAL_AddressableLEDHandle handle) {
auto led = addressableLEDHandles->Get(handle);
if (!led) {
return;
}
uint32_t session = led->led->getSystemInterface()->getHandle();
hal::HAL_NiFpga_CloseHmb(session, HmbName);
addressableLEDHandles->Free(handle);
}

View File

@@ -0,0 +1,66 @@
// 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 "FPGACalls.h"
#include <cerrno>
#include "dlfcn.h"
#include "hal/Errors.h"
static void* NiFpgaLibrary = nullptr;
namespace hal {
HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
HAL_NiFpga_OpenHmbFunc HAL_NiFpga_OpenHmb;
HAL_NiFpga_CloseHmbFunc HAL_NiFpga_CloseHmb;
namespace init {
int InitializeFPGA() {
NiFpgaLibrary = dlopen("libNiFpga.so", RTLD_LAZY);
if (!NiFpgaLibrary) {
return errno;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
HAL_NiFpga_ReserveIrqContext =
reinterpret_cast<HAL_NiFpga_ReserveIrqContextFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_ReserveIrqContext"));
HAL_NiFpga_UnreserveIrqContext =
reinterpret_cast<HAL_NiFpga_UnreserveIrqContextFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_UnreserveIrqContext"));
HAL_NiFpga_WaitOnIrqs = reinterpret_cast<HAL_NiFpga_WaitOnIrqsFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_WaitOnIrqs"));
HAL_NiFpga_AcknowledgeIrqs = reinterpret_cast<HAL_NiFpga_AcknowledgeIrqsFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_AcknowledgeIrqs"));
HAL_NiFpga_OpenHmb = reinterpret_cast<HAL_NiFpga_OpenHmbFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_OpenHmb"));
HAL_NiFpga_CloseHmb = reinterpret_cast<HAL_NiFpga_CloseHmbFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_CloseHmb"));
#pragma GCC diagnostic pop
if (HAL_NiFpga_ReserveIrqContext == nullptr ||
HAL_NiFpga_UnreserveIrqContext == nullptr ||
HAL_NiFpga_WaitOnIrqs == nullptr ||
HAL_NiFpga_AcknowledgeIrqs == nullptr || HAL_NiFpga_OpenHmb == nullptr ||
HAL_NiFpga_CloseHmb == nullptr) {
HAL_NiFpga_ReserveIrqContext = nullptr;
HAL_NiFpga_UnreserveIrqContext = nullptr;
HAL_NiFpga_WaitOnIrqs = nullptr;
HAL_NiFpga_AcknowledgeIrqs = nullptr;
HAL_NiFpga_OpenHmb = nullptr;
HAL_NiFpga_CloseHmb = nullptr;
dlclose(NiFpgaLibrary);
NiFpgaLibrary = nullptr;
return NO_AVAILABLE_RESOURCES;
}
return HAL_SUCCESS;
}
} // namespace init
} // namespace hal

View File

@@ -0,0 +1,46 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <FRC_FPGA_ChipObject/fpgainterfacecapi/NiFpga.h>
namespace hal {
namespace init {
[[nodiscard]] int InitializeFPGA();
} // namespace init
using HAL_NiFpga_ReserveIrqContextFunc =
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext* context);
extern HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
using HAL_NiFpga_UnreserveIrqContextFunc =
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext context);
extern HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
using HAL_NiFpga_WaitOnIrqsFunc = NiFpga_Status (*)(
NiFpga_Session session, NiFpga_IrqContext context, uint32_t irqs,
uint32_t timeout, uint32_t* irqsAsserted, NiFpga_Bool* timedOut);
extern HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
using HAL_NiFpga_AcknowledgeIrqsFunc = NiFpga_Status (*)(NiFpga_Session session,
uint32_t irqs);
extern HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
using HAL_NiFpga_OpenHmbFunc = NiFpga_Status (*)(const NiFpga_Session session,
const char* memoryName,
size_t* memorySize,
void** virtualAddress);
extern HAL_NiFpga_OpenHmbFunc HAL_NiFpga_OpenHmb;
using HAL_NiFpga_CloseHmbFunc = NiFpga_Status (*)(const NiFpga_Session session,
const char* memoryName);
extern HAL_NiFpga_CloseHmbFunc HAL_NiFpga_CloseHmb;
} // namespace hal

View File

@@ -13,39 +13,66 @@
#include <FRC_NetworkCommunication/FRCComm.h>
#include <FRC_NetworkCommunication/NetCommRPCProxy_Occur.h>
#include <fmt/format.h>
#include <wpi/EventVector.h>
#include <wpi/SafeThread.h>
#include <wpi/SmallVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include "HALInitializer.h"
#include "hal/DriverStation.h"
#include "hal/Errors.h"
static_assert(sizeof(int32_t) >= sizeof(int),
"FRC_NetworkComm status variable is larger than 32 bits");
namespace {
struct HAL_JoystickAxesInt {
int16_t count;
int16_t axes[HAL_kMaxJoystickAxes];
};
} // namespace
static constexpr int kJoystickPorts = 6;
namespace {
struct JoystickDataCache {
JoystickDataCache() { std::memset(this, 0, sizeof(*this)); }
void Update();
HAL_JoystickAxes axes[HAL_kMaxJoysticks];
HAL_JoystickPOVs povs[HAL_kMaxJoysticks];
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
HAL_AllianceStationID allianceStation;
float matchTime;
bool updated;
};
static_assert(std::is_standard_layout_v<JoystickDataCache>);
// static_assert(std::is_trivial_v<JoystickDataCache>);
struct FRCDriverStation {
wpi::EventVector newDataEvents;
};
} // namespace
static ::FRCDriverStation* driverStation;
// Message and Data variables
static wpi::mutex msgMutex;
static int32_t HAL_GetJoystickAxesInternal(int32_t joystickNum,
HAL_JoystickAxes* axes) {
HAL_JoystickAxesInt axesInt;
HAL_JoystickAxesInt netcommAxes;
int retVal = FRC_NetworkCommunication_getJoystickAxes(
joystickNum, reinterpret_cast<JoystickAxes_t*>(&axesInt),
joystickNum, reinterpret_cast<JoystickAxes_t*>(&netcommAxes),
HAL_kMaxJoystickAxes);
// copy integer values to double values
axes->count = axesInt.count;
axes->count = netcommAxes.count;
// current scaling is -128 to 127, can easily be patched in the future by
// changing this function.
for (int32_t i = 0; i < axesInt.count; i++) {
int8_t value = axesInt.axes[i];
for (int32_t i = 0; i < netcommAxes.count; i++) {
int8_t value = netcommAxes.axes[i];
axes->raw[i] = value;
if (value < 0) {
axes->axes[i] = value / 128.0;
} else {
@@ -68,6 +95,30 @@ static int32_t HAL_GetJoystickButtonsInternal(int32_t joystickNum,
return FRC_NetworkCommunication_getJoystickButtons(
joystickNum, &buttons->buttons, &buttons->count);
}
void JoystickDataCache::Update() {
for (int i = 0; i < HAL_kMaxJoysticks; i++) {
HAL_GetJoystickAxesInternal(i, &axes[i]);
HAL_GetJoystickPOVsInternal(i, &povs[i]);
HAL_GetJoystickButtonsInternal(i, &buttons[i]);
}
FRC_NetworkCommunication_getAllianceStation(
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
FRC_NetworkCommunication_getMatchTime(&matchTime);
}
#define CHECK_JOYSTICK_NUMBER(stickNum) \
if ((stickNum) < 0 || (stickNum) >= HAL_kMaxJoysticks) \
return PARAMETER_OUT_OF_RANGE
static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentCache = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
static wpi::mutex cacheMutex;
/**
* Retrieve the Joystick Descriptor for particular slot.
*
@@ -102,12 +153,6 @@ static int32_t HAL_GetJoystickDescriptorInternal(int32_t joystickNum,
return retval;
}
static int32_t HAL_GetControlWordInternal(HAL_ControlWord* controlWord) {
std::memset(controlWord, 0, sizeof(HAL_ControlWord));
return FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(controlWord));
}
static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
MatchType_t matchType = MatchType_t::kMatchType_none;
info->gameSpecificMessageSize = sizeof(info->gameSpecificMessage);
@@ -126,16 +171,11 @@ static int32_t HAL_GetMatchInfoInternal(HAL_MatchInfo* info) {
return status;
}
static wpi::mutex* newDSDataAvailableMutex;
static wpi::condition_variable* newDSDataAvailableCond;
static int newDSDataAvailableCounter{0};
namespace hal::init {
void InitializeFRCDriverStation() {
static wpi::mutex newMutex;
newDSDataAvailableMutex = &newMutex;
static wpi::condition_variable newCond;
newDSDataAvailableCond = &newCond;
std::memset(&newestControlWord, 0, sizeof(newestControlWord));
static FRCDriverStation ds;
driverStation = &ds;
}
} // namespace hal::init
@@ -248,20 +288,39 @@ int32_t HAL_SendConsoleLine(const char* line) {
}
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
return HAL_GetControlWordInternal(controlWord);
std::scoped_lock lock{cacheMutex};
*controlWord = newestControlWord;
return 0;
}
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
return HAL_GetJoystickAxesInternal(joystickNum, axes);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*axes = currentRead->axes[joystickNum];
return 0;
}
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
return HAL_GetJoystickPOVsInternal(joystickNum, povs);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*povs = currentRead->povs[joystickNum];
return 0;
}
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) {
return HAL_GetJoystickButtonsInternal(joystickNum, buttons);
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{cacheMutex};
*buttons = currentRead->buttons[joystickNum];
return 0;
}
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons) {
std::scoped_lock lock{cacheMutex};
std::memcpy(axes, currentRead->axes, sizeof(currentRead->axes));
std::memcpy(povs, currentRead->povs, sizeof(currentRead->povs));
std::memcpy(buttons, currentRead->buttons, sizeof(currentRead->buttons));
}
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
@@ -274,10 +333,8 @@ int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
}
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
HAL_AllianceStationID allianceStation;
*status = FRC_NetworkCommunication_getAllianceStation(
reinterpret_cast<AllianceStationID_t*>(&allianceStation));
return allianceStation;
std::scoped_lock lock{cacheMutex};
return currentRead->allianceStation;
}
HAL_Bool HAL_GetJoystickIsXbox(int32_t joystickNum) {
@@ -317,6 +374,7 @@ void HAL_FreeJoystickName(char* name) {
}
int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
CHECK_JOYSTICK_NUMBER(joystickNum);
HAL_JoystickDescriptor joystickDesc;
if (HAL_GetJoystickDescriptor(joystickNum, &joystickDesc) < 0) {
return -1;
@@ -327,14 +385,14 @@ int32_t HAL_GetJoystickAxisType(int32_t joystickNum, int32_t axis) {
int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
int32_t leftRumble, int32_t rightRumble) {
CHECK_JOYSTICK_NUMBER(joystickNum);
return FRC_NetworkCommunication_setJoystickOutputs(joystickNum, outputs,
leftRumble, rightRumble);
}
double HAL_GetMatchTime(int32_t* status) {
float matchTime;
*status = FRC_NetworkCommunication_getMatchTime(&matchTime);
return matchTime;
std::scoped_lock lock{cacheMutex};
return currentRead->matchTime;
}
void HAL_ObserveUserProgramStarting(void) {
@@ -357,55 +415,6 @@ void HAL_ObserveUserProgramTest(void) {
FRC_NetworkCommunication_observeUserProgramTest();
}
static int& GetThreadLocalLastCount() {
// There is a rollover error condition here. At Packet# = n * (uintmax), this
// will return false when instead it should return true. However, this at a
// 20ms rate occurs once every 2.7 years of DS connected runtime, so not
// worth the cycles to check.
thread_local int lastCount{0};
return lastCount;
}
HAL_Bool HAL_IsNewControlData(void) {
std::scoped_lock lock{*newDSDataAvailableMutex};
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount == currentCount) {
return false;
}
lastCount = currentCount;
return true;
}
void HAL_WaitForDSData(void) {
HAL_WaitForDSDataTimeout(0);
}
HAL_Bool HAL_WaitForDSDataTimeout(double timeout) {
std::unique_lock lock{*newDSDataAvailableMutex};
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount != currentCount) {
lastCount = currentCount;
return true;
}
auto timeoutTime =
std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
while (newDSDataAvailableCounter == currentCount) {
if (timeout > 0) {
auto timedOut = newDSDataAvailableCond->wait_until(lock, timeoutTime);
if (timedOut == std::cv_status::timeout) {
return false;
}
} else {
newDSDataAvailableCond->wait(lock);
}
}
lastCount = newDSDataAvailableCounter;
return true;
}
// Constant number to be used for our occur handle
constexpr int32_t refNumber = 42;
@@ -415,45 +424,48 @@ static void newDataOccur(uint32_t refNum) {
if (refNum != refNumber) {
return;
}
std::scoped_lock lock{*newDSDataAvailableMutex};
// Notify all threads
++newDSDataAvailableCounter;
newDSDataAvailableCond->notify_all();
cacheToUpdate->Update();
{
std::scoped_lock lock{cacheMutex};
std::swap(currentCache, cacheToUpdate);
currentCache->updated = true;
}
driverStation->newDataEvents.Wakeup();
}
/*
* Call this to initialize the driver station communication. This will properly
* handle multiple calls. However note that this CANNOT be called from a library
* that interfaces with LabVIEW.
*/
void HAL_InitializeDriverStation(void) {
static std::atomic_bool initialized{false};
static wpi::mutex initializeMutex;
// Initial check, as if it's true initialization has finished
if (initialized) {
return;
void HAL_RefreshDSData(void) {
HAL_ControlWord controlWord;
std::memset(&controlWord, 0, sizeof(controlWord));
FRC_NetworkCommunication_getControlWord(
reinterpret_cast<ControlWord_t*>(&controlWord));
std::scoped_lock lock{cacheMutex};
if (currentCache->updated) {
std::swap(currentCache, currentRead);
currentCache->updated = false;
}
newestControlWord = controlWord;
}
std::scoped_lock lock(initializeMutex);
// Second check in case another thread was waiting
if (initialized) {
return;
}
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
hal::init::CheckInit();
driverStation->newDataEvents.Add(handle);
}
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
driverStation->newDataEvents.Remove(handle);
}
HAL_Bool HAL_GetOutputsEnabled(void) {
return FRC_NetworkCommunication_getWatchdogActive();
}
} // extern "C"
namespace hal {
void InitializeDriverStation() {
// Set up the occur function internally with NetComm
NetCommRPCProxy_SetOccurFuncPointer(newDataOccur);
// Set up our occur reference number
setNewDataOccurRef(refNumber);
initialized = true;
}
/*
* Releases the DS Mutex to allow proper shutdown of any threads that are
* waiting on it.
*/
void HAL_ReleaseDSMutex(void) {
newDataOccur(refNumber);
}
} // extern "C"
} // namespace hal

View File

@@ -21,6 +21,7 @@
#include <wpi/mutex.h>
#include <wpi/timestamp.h>
#include "FPGACalls.h"
#include "HALInitializer.h"
#include "HALInternal.h"
#include "hal/ChipObject.h"
@@ -40,6 +41,7 @@ static uint64_t dsStartTime;
using namespace hal;
namespace hal {
void InitializeDriverStation();
namespace init {
void InitializeHAL() {
InitializeCTREPCM();
@@ -80,6 +82,7 @@ void InitializeHAL() {
} // namespace init
void ReleaseFPGAInterrupt(int32_t interruptNumber) {
hal::init::CheckInit();
if (!global) {
return;
}
@@ -250,6 +253,7 @@ HAL_RuntimeType HAL_GetRuntimeType(void) {
}
int32_t HAL_GetFPGAVersion(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -258,6 +262,7 @@ int32_t HAL_GetFPGAVersion(int32_t* status) {
}
int64_t HAL_GetFPGARevision(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -266,6 +271,7 @@ int64_t HAL_GetFPGARevision(int32_t* status) {
}
uint64_t HAL_GetFPGATime(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
@@ -312,6 +318,7 @@ uint64_t HAL_ExpandFPGATime(uint32_t unexpandedLower, int32_t* status) {
}
HAL_Bool HAL_GetFPGAButton(int32_t* status) {
hal::init::CheckInit();
if (!global) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;
@@ -320,6 +327,7 @@ HAL_Bool HAL_GetFPGAButton(int32_t* status) {
}
HAL_Bool HAL_GetSystemActive(int32_t* status) {
hal::init::CheckInit();
if (!watchdog) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;
@@ -328,6 +336,7 @@ HAL_Bool HAL_GetSystemActive(int32_t* status) {
}
HAL_Bool HAL_GetBrownedOut(int32_t* status) {
hal::init::CheckInit();
if (!watchdog) {
*status = NiFpga_Status_ResourceNotInitialized;
return false;
@@ -393,6 +402,11 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
return true;
}
int fpgaInit = hal::init::InitializeFPGA();
if (fpgaInit != HAL_SUCCESS) {
return false;
}
hal::init::InitializeHAL();
hal::init::HAL_IsInitialized.store(true);
@@ -425,13 +439,9 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
return false;
}
status = InterruptManager::Initialize(global->getSystemInterface());
InterruptManager::Initialize(global->getSystemInterface());
if (status != 0) {
return false;
}
HAL_InitializeDriverStation();
hal::InitializeDriverStation();
dsStartTime = HAL_GetFPGATime(&status);
if (status != 0) {

View File

@@ -6,34 +6,11 @@
#include <fmt/format.h>
#include "FPGACalls.h"
#include "HALInternal.h"
#include "dlfcn.h"
#include "hal/Errors.h"
// Low level FPGA calls
using HAL_NiFpga_ReserveIrqContextFunc =
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext* context);
static HAL_NiFpga_ReserveIrqContextFunc HAL_NiFpga_ReserveIrqContext;
using HAL_NiFpga_UnreserveIrqContextFunc =
NiFpga_Status (*)(NiFpga_Session session, NiFpga_IrqContext context);
static HAL_NiFpga_UnreserveIrqContextFunc HAL_NiFpga_UnreserveIrqContext;
using HAL_NiFpga_WaitOnIrqsFunc = NiFpga_Status (*)(
NiFpga_Session session, NiFpga_IrqContext context, uint32_t irqs,
uint32_t timeout, uint32_t* irqsAsserted, NiFpga_Bool* timedOut);
static HAL_NiFpga_WaitOnIrqsFunc HAL_NiFpga_WaitOnIrqs;
using HAL_NiFpga_AcknowledgeIrqsFunc = NiFpga_Status (*)(NiFpga_Session session,
uint32_t irqs);
static HAL_NiFpga_AcknowledgeIrqsFunc HAL_NiFpga_AcknowledgeIrqs;
static void* NiFpgaLibrary = nullptr;
using namespace hal;
InterruptManager& InterruptManager::GetInstance() {
@@ -41,37 +18,9 @@ InterruptManager& InterruptManager::GetInstance() {
return manager;
}
int32_t InterruptManager::Initialize(tSystemInterface* baseSystem) {
void InterruptManager::Initialize(tSystemInterface* baseSystem) {
auto& manager = GetInstance();
manager.fpgaSession = baseSystem->getHandle();
NiFpgaLibrary = dlopen("libNiFpga.so", RTLD_LAZY);
if (!NiFpgaLibrary) {
return errno;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
HAL_NiFpga_ReserveIrqContext =
reinterpret_cast<HAL_NiFpga_ReserveIrqContextFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_ReserveIrqContext"));
HAL_NiFpga_UnreserveIrqContext =
reinterpret_cast<HAL_NiFpga_UnreserveIrqContextFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_UnreserveIrqContext"));
HAL_NiFpga_WaitOnIrqs = reinterpret_cast<HAL_NiFpga_WaitOnIrqsFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_WaitOnIrqs"));
HAL_NiFpga_AcknowledgeIrqs = reinterpret_cast<HAL_NiFpga_AcknowledgeIrqsFunc>(
dlsym(NiFpgaLibrary, "NiFpgaDll_AcknowledgeIrqs"));
#pragma GCC diagnostic pop
if (HAL_NiFpga_ReserveIrqContext == nullptr ||
HAL_NiFpga_UnreserveIrqContext == nullptr ||
HAL_NiFpga_WaitOnIrqs == nullptr ||
HAL_NiFpga_AcknowledgeIrqs == nullptr) {
return NO_AVAILABLE_RESOURCES;
}
return HAL_SUCCESS;
}
NiFpga_IrqContext InterruptManager::GetContext() noexcept {

View File

@@ -0,0 +1,450 @@
// 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 <jni.h>
#include <cassert>
#include <fmt/format.h>
#include <wpi/jni_util.h>
#include "HALUtil.h"
#include "edu_wpi_first_hal_DriverStationJNI.h"
#include "hal/DriverStation.h"
#include "hal/FRCUsageReporting.h"
#include "hal/HALBase.h"
// TODO Static asserts
using namespace hal;
using namespace wpi::java;
extern "C" {
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: observeUserProgramStarting
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramStarting
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramStarting();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: observeUserProgramDisabled
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramDisabled
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramDisabled();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: observeUserProgramAutonomous
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramAutonomous
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramAutonomous();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: observeUserProgramTeleop
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramTeleop
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramTeleop();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: observeUserProgramTest
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_observeUserProgramTest
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramTest();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: report
* Signature: (IIILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_report
(JNIEnv* paramEnv, jclass, jint paramResource, jint paramInstanceNumber,
jint paramContext, jstring paramFeature)
{
JStringRef featureStr{paramEnv, paramFeature};
jint returnValue = HAL_Report(paramResource, paramInstanceNumber,
paramContext, featureStr.c_str());
return returnValue;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: nativeGetControlWord
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_nativeGetControlWord
(JNIEnv*, jclass)
{
static_assert(sizeof(HAL_ControlWord) == sizeof(jint),
"Java int must match the size of control word");
HAL_ControlWord controlWord;
HAL_GetControlWord(&controlWord);
jint retVal = 0;
std::memcpy(&retVal, &controlWord, sizeof(HAL_ControlWord));
return retVal;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: nativeGetAllianceStation
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_nativeGetAllianceStation
(JNIEnv*, jclass)
{
int32_t status = 0;
auto allianceStation = HAL_GetAllianceStation(&status);
return static_cast<jint>(allianceStation);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickAxesRaw
* Signature: (B[I)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxesRaw
(JNIEnv* env, jclass, jbyte joystickNum, jintArray axesRawArray)
{
HAL_JoystickAxes axes;
HAL_GetJoystickAxes(joystickNum, &axes);
jsize javaSize = env->GetArrayLength(axesRawArray);
if (axes.count > javaSize) {
ThrowIllegalArgumentException(
env,
fmt::format("Native array size larger then passed in java array "
"size\nNative Size: {} Java Size: {}",
static_cast<int>(axes.count), static_cast<int>(javaSize)));
return 0;
}
jint raw[HAL_kMaxJoystickAxes];
for (int16_t i = 0; i < axes.count; i++) {
raw[i] = axes.raw[i];
}
env->SetIntArrayRegion(axesRawArray, 0, axes.count, raw);
return axes.count;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickAxes
* Signature: (B[F)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxes
(JNIEnv* env, jclass, jbyte joystickNum, jfloatArray axesArray)
{
HAL_JoystickAxes axes;
HAL_GetJoystickAxes(joystickNum, &axes);
jsize javaSize = env->GetArrayLength(axesArray);
if (axes.count > javaSize) {
ThrowIllegalArgumentException(
env,
fmt::format("Native array size larger then passed in java array "
"size\nNative Size: {} Java Size: {}",
static_cast<int>(axes.count), static_cast<int>(javaSize)));
return 0;
}
env->SetFloatArrayRegion(axesArray, 0, axes.count, axes.axes);
return axes.count;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickPOVs
* Signature: (B[S)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickPOVs
(JNIEnv* env, jclass, jbyte joystickNum, jshortArray povsArray)
{
HAL_JoystickPOVs povs;
HAL_GetJoystickPOVs(joystickNum, &povs);
jsize javaSize = env->GetArrayLength(povsArray);
if (povs.count > javaSize) {
ThrowIllegalArgumentException(
env,
fmt::format("Native array size larger then passed in java array "
"size\nNative Size: {} Java Size: {}",
static_cast<int>(povs.count), static_cast<int>(javaSize)));
return 0;
}
env->SetShortArrayRegion(povsArray, 0, povs.count, povs.povs);
return povs.count;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getAllJoystickData
* Signature: ([F[B[S[J)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getAllJoystickData
(JNIEnv* env, jclass cls, jfloatArray axesArray, jbyteArray rawAxesArray,
jshortArray povsArray, jlongArray buttonsAndMetadataArray)
{
HAL_JoystickAxes axes[HAL_kMaxJoysticks];
HAL_JoystickPOVs povs[HAL_kMaxJoysticks];
HAL_JoystickButtons buttons[HAL_kMaxJoysticks];
HAL_GetAllJoystickData(axes, povs, buttons);
CriticalJFloatArrayRef jAxes(env, axesArray);
CriticalJByteArrayRef jRawAxes(env, rawAxesArray);
CriticalJShortArrayRef jPovs(env, povsArray);
CriticalJLongArrayRef jButtons(env, buttonsAndMetadataArray);
static_assert(sizeof(jAxes[0]) == sizeof(axes[0].axes[0]));
static_assert(sizeof(jRawAxes[0]) == sizeof(axes[0].raw[0]));
static_assert(sizeof(jPovs[0]) == sizeof(povs[0].povs[0]));
for (size_t i = 0; i < HAL_kMaxJoysticks; i++) {
std::memcpy(&jAxes[i * HAL_kMaxJoystickAxes], axes[i].axes,
sizeof(axes[i].axes));
std::memcpy(&jRawAxes[i * HAL_kMaxJoystickAxes], axes[i].raw,
sizeof(axes[i].raw));
std::memcpy(&jPovs[i * HAL_kMaxJoystickPOVs], povs[i].povs,
sizeof(povs[i].povs));
jButtons[i * 4] = axes[i].count;
jButtons[(i * 4) + 1] = povs[i].count;
jButtons[(i * 4) + 2] = buttons[i].count;
jButtons[(i * 4) + 3] = buttons[i].buttons;
}
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickButtons
* Signature: (BLjava/lang/Object;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickButtons
(JNIEnv* env, jclass, jbyte joystickNum, jobject count)
{
HAL_JoystickButtons joystickButtons;
HAL_GetJoystickButtons(joystickNum, &joystickButtons);
jbyte* countPtr =
reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(count));
*countPtr = joystickButtons.count;
return joystickButtons.buttons;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: setJoystickOutputs
* Signature: (BISS)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_setJoystickOutputs
(JNIEnv*, jclass, jbyte port, jint outputs, jshort leftRumble,
jshort rightRumble)
{
return HAL_SetJoystickOutputs(port, outputs, leftRumble, rightRumble);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickIsXbox
* Signature: (B)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickIsXbox
(JNIEnv*, jclass, jbyte port)
{
return HAL_GetJoystickIsXbox(port);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickType
* Signature: (B)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickType
(JNIEnv*, jclass, jbyte port)
{
return HAL_GetJoystickType(port);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickName
* Signature: (B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickName
(JNIEnv* env, jclass, jbyte port)
{
char* joystickName = HAL_GetJoystickName(port);
jstring str = MakeJString(env, joystickName);
HAL_FreeJoystickName(joystickName);
return str;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getJoystickAxisType
* Signature: (BB)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getJoystickAxisType
(JNIEnv*, jclass, jbyte joystickNum, jbyte axis)
{
return HAL_GetJoystickAxisType(joystickNum, axis);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getMatchTime
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getMatchTime
(JNIEnv* env, jclass)
{
int32_t status = 0;
return HAL_GetMatchTime(&status);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getMatchInfo
* Signature: (Ljava/lang/Object;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getMatchInfo
(JNIEnv* env, jclass, jobject info)
{
HAL_MatchInfo matchInfo;
auto status = HAL_GetMatchInfo(&matchInfo);
if (status == 0) {
SetMatchInfoObject(env, info, matchInfo);
}
return status;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: sendError
* Signature: (ZIZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_sendError
(JNIEnv* env, jclass, jboolean isError, jint errorCode, jboolean isLVCode,
jstring details, jstring location, jstring callStack, jboolean printMsg)
{
JStringRef detailsStr{env, details};
JStringRef locationStr{env, location};
JStringRef callStackStr{env, callStack};
jint returnValue =
HAL_SendError(isError, errorCode, isLVCode, detailsStr.c_str(),
locationStr.c_str(), callStackStr.c_str(), printMsg);
return returnValue;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: sendConsoleLine
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_sendConsoleLine
(JNIEnv* env, jclass, jstring line)
{
JStringRef lineStr{env, line};
jint returnValue = HAL_SendConsoleLine(lineStr.c_str());
return returnValue;
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: refreshDSData
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_refreshDSData
(JNIEnv*, jclass)
{
HAL_RefreshDSData();
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: provideNewDataEventHandle
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_provideNewDataEventHandle
(JNIEnv*, jclass, jint handle)
{
HAL_ProvideNewDataEventHandle(handle);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: removeNewDataEventHandle
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_removeNewDataEventHandle
(JNIEnv*, jclass, jint handle)
{
HAL_RemoveNewDataEventHandle(handle);
}
/*
* Class: edu_wpi_first_hal_DriverStationJNI
* Method: getOutputsActive
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_DriverStationJNI_getOutputsActive
(JNIEnv*, jclass)
{
return HAL_GetOutputsEnabled();
}
} // extern "C"

View File

@@ -106,310 +106,6 @@ Java_edu_wpi_first_hal_HAL_simPeriodicAfterNative
HAL_SimPeriodicAfter();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: observeUserProgramStarting
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_observeUserProgramStarting
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramStarting();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: observeUserProgramDisabled
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_observeUserProgramDisabled
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramDisabled();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: observeUserProgramAutonomous
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_observeUserProgramAutonomous
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramAutonomous();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: observeUserProgramTeleop
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_observeUserProgramTeleop
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramTeleop();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: observeUserProgramTest
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_observeUserProgramTest
(JNIEnv*, jclass)
{
HAL_ObserveUserProgramTest();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: report
* Signature: (IIILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_report
(JNIEnv* paramEnv, jclass, jint paramResource, jint paramInstanceNumber,
jint paramContext, jstring paramFeature)
{
JStringRef featureStr{paramEnv, paramFeature};
jint returnValue = HAL_Report(paramResource, paramInstanceNumber,
paramContext, featureStr.c_str());
return returnValue;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: nativeGetControlWord
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_nativeGetControlWord
(JNIEnv*, jclass)
{
static_assert(sizeof(HAL_ControlWord) == sizeof(jint),
"Java int must match the size of control word");
HAL_ControlWord controlWord;
HAL_GetControlWord(&controlWord);
jint retVal = 0;
std::memcpy(&retVal, &controlWord, sizeof(HAL_ControlWord));
return retVal;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: nativeGetAllianceStation
* Signature: ()I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_nativeGetAllianceStation
(JNIEnv*, jclass)
{
int32_t status = 0;
auto allianceStation = HAL_GetAllianceStation(&status);
return static_cast<jint>(allianceStation);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickAxes
* Signature: (B[F)S
*/
JNIEXPORT jshort JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickAxes
(JNIEnv* env, jclass, jbyte joystickNum, jfloatArray axesArray)
{
HAL_JoystickAxes axes;
HAL_GetJoystickAxes(joystickNum, &axes);
jsize javaSize = env->GetArrayLength(axesArray);
if (axes.count > javaSize) {
ThrowIllegalArgumentException(
env,
fmt::format("Native array size larger then passed in java array "
"size\nNative Size: {} Java Size: {}",
static_cast<int>(axes.count), static_cast<int>(javaSize)));
return 0;
}
env->SetFloatArrayRegion(axesArray, 0, axes.count, axes.axes);
return axes.count;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickPOVs
* Signature: (B[S)S
*/
JNIEXPORT jshort JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickPOVs
(JNIEnv* env, jclass, jbyte joystickNum, jshortArray povsArray)
{
HAL_JoystickPOVs povs;
HAL_GetJoystickPOVs(joystickNum, &povs);
jsize javaSize = env->GetArrayLength(povsArray);
if (povs.count > javaSize) {
ThrowIllegalArgumentException(
env,
fmt::format("Native array size larger then passed in java array "
"size\nNative Size: {} Java Size: {}",
static_cast<int>(povs.count), static_cast<int>(javaSize)));
return 0;
}
env->SetShortArrayRegion(povsArray, 0, povs.count, povs.povs);
return povs.count;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickButtons
* Signature: (BLjava/lang/Object;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickButtons
(JNIEnv* env, jclass, jbyte joystickNum, jobject count)
{
HAL_JoystickButtons joystickButtons;
HAL_GetJoystickButtons(joystickNum, &joystickButtons);
jbyte* countPtr =
reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(count));
*countPtr = joystickButtons.count;
return joystickButtons.buttons;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: setJoystickOutputs
* Signature: (BISS)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_setJoystickOutputs
(JNIEnv*, jclass, jbyte port, jint outputs, jshort leftRumble,
jshort rightRumble)
{
return HAL_SetJoystickOutputs(port, outputs, leftRumble, rightRumble);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickIsXbox
* Signature: (B)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickIsXbox
(JNIEnv*, jclass, jbyte port)
{
return HAL_GetJoystickIsXbox(port);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickType
* Signature: (B)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickType
(JNIEnv*, jclass, jbyte port)
{
return HAL_GetJoystickType(port);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickName
* Signature: (B)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickName
(JNIEnv* env, jclass, jbyte port)
{
char* joystickName = HAL_GetJoystickName(port);
jstring str = MakeJString(env, joystickName);
HAL_FreeJoystickName(joystickName);
return str;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getJoystickAxisType
* Signature: (BB)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_getJoystickAxisType
(JNIEnv*, jclass, jbyte joystickNum, jbyte axis)
{
return HAL_GetJoystickAxisType(joystickNum, axis);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: isNewControlData
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_HAL_isNewControlData
(JNIEnv*, jclass)
{
return static_cast<jboolean>(HAL_IsNewControlData());
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: waitForDSData
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_waitForDSData
(JNIEnv* env, jclass)
{
HAL_WaitForDSData();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: releaseDSMutex
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_hal_HAL_releaseDSMutex
(JNIEnv* env, jclass)
{
HAL_ReleaseDSMutex();
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: waitForDSDataTimeout
* Signature: (D)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_hal_HAL_waitForDSDataTimeout
(JNIEnv*, jclass, jdouble timeout)
{
return static_cast<jboolean>(HAL_WaitForDSDataTimeout(timeout));
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getMatchTime
* Signature: ()D
*/
JNIEXPORT jdouble JNICALL
Java_edu_wpi_first_hal_HAL_getMatchTime
(JNIEnv* env, jclass)
{
int32_t status = 0;
return HAL_GetMatchTime(&status);
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getSystemActive
@@ -440,58 +136,6 @@ Java_edu_wpi_first_hal_HAL_getBrownedOut
return val;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getMatchInfo
* Signature: (Ljava/lang/Object;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_getMatchInfo
(JNIEnv* env, jclass, jobject info)
{
HAL_MatchInfo matchInfo;
auto status = HAL_GetMatchInfo(&matchInfo);
if (status == 0) {
SetMatchInfoObject(env, info, matchInfo);
}
return status;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: sendError
* Signature: (ZIZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_sendError
(JNIEnv* env, jclass, jboolean isError, jint errorCode, jboolean isLVCode,
jstring details, jstring location, jstring callStack, jboolean printMsg)
{
JStringRef detailsStr{env, details};
JStringRef locationStr{env, location};
JStringRef callStackStr{env, callStack};
jint returnValue =
HAL_SendError(isError, errorCode, isLVCode, detailsStr.c_str(),
locationStr.c_str(), callStackStr.c_str(), printMsg);
return returnValue;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: sendConsoleLine
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_HAL_sendConsoleLine
(JNIEnv* env, jclass, jstring line)
{
JStringRef lineStr{env, line};
jint returnValue = HAL_SendConsoleLine(lineStr.c_str());
return returnValue;
}
/*
* Class: edu_wpi_first_hal_HAL
* Method: getPortWithModule

View File

@@ -6,6 +6,8 @@
#include <stdint.h>
#include <wpi/Synchronization.h>
#include "hal/DriverStationTypes.h"
#include "hal/Types.h"
@@ -87,6 +89,9 @@ int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs);
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons);
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons);
/**
* Retrieves the Joystick Descriptor for particular slot.
*
@@ -183,6 +188,11 @@ int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
*/
double HAL_GetMatchTime(int32_t* status);
/**
* Gets if outputs are enabled by the control system.
*/
HAL_Bool HAL_GetOutputsEnabled(void);
/**
* Gets info about a specific match.
*
@@ -191,44 +201,10 @@ double HAL_GetMatchTime(int32_t* status);
*/
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info);
/**
* Releases the DS Mutex to allow proper shutdown of any threads that are
* waiting on it.
*/
void HAL_ReleaseDSMutex(void);
void HAL_RefreshDSData(void);
/**
* Has a new control packet from the driver station arrived since the last
* time this function was called?
*
* @return true if the control data has been updated since the last call
*/
HAL_Bool HAL_IsNewControlData(void);
/**
* Waits for the newest DS packet to arrive. Note that this is a blocking call.
* Checks if new control data has arrived since the last HAL_WaitForDSData or
* HAL_IsNewControlData call. If new data has not arrived, waits for new data
* to arrive. Otherwise, returns immediately.
*/
void HAL_WaitForDSData(void);
/**
* Waits for the newest DS packet to arrive. If timeout is <= 0, this will wait
* forever. Otherwise, it will wait until either a new packet, or the timeout
* time has passed.
*
* @param[in] timeout timeout in seconds
* @return true for new data, false for timeout
*/
HAL_Bool HAL_WaitForDSDataTimeout(double timeout);
/**
* Initializes the driver station communication. This will properly
* handle multiple calls. However note that this CANNOT be called from a library
* that interfaces with LabVIEW.
*/
void HAL_InitializeDriverStation(void);
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle);
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle);
/**
* Sets the program starting flag in the DS.

View File

@@ -69,6 +69,7 @@ HAL_ENUM(HAL_MatchType) {
struct HAL_JoystickAxes {
int16_t count;
float axes[HAL_kMaxJoystickAxes];
uint8_t raw[HAL_kMaxJoystickAxes];
};
typedef struct HAL_JoystickAxes HAL_JoystickAxes;

View File

@@ -14,7 +14,7 @@ namespace hal {
class InterruptManager {
public:
static InterruptManager& GetInstance();
static int32_t Initialize(tSystemInterface* baseSystem);
static void Initialize(tSystemInterface* baseSystem);
NiFpga_IrqContext GetContext() noexcept;
void ReleaseContext(NiFpga_IrqContext context) noexcept;

View File

@@ -14,32 +14,78 @@
#include <string>
#include <fmt/format.h>
#include <wpi/EventVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include "HALInitializer.h"
#include "hal/Errors.h"
#include "hal/cpp/fpga_clock.h"
#include "hal/simulation/MockHooks.h"
#include "mockdata/DriverStationDataInternal.h"
static wpi::mutex msgMutex;
static wpi::condition_variable* newDSDataAvailableCond;
static wpi::mutex newDSDataAvailableMutex;
static int newDSDataAvailableCounter{0};
static std::atomic_bool isFinalized{false};
static std::atomic<HALSIM_SendErrorHandler> sendErrorHandler{nullptr};
static std::atomic<HALSIM_SendConsoleLineHandler> sendConsoleLineHandler{
nullptr};
using namespace hal;
static constexpr int kJoystickPorts = 6;
namespace {
struct JoystickDataCache {
JoystickDataCache() { std::memset(this, 0, sizeof(*this)); }
void Update();
HAL_JoystickAxes axes[kJoystickPorts];
HAL_JoystickPOVs povs[kJoystickPorts];
HAL_JoystickButtons buttons[kJoystickPorts];
HAL_AllianceStationID allianceStation;
double matchTime;
bool updated;
};
static_assert(std::is_standard_layout_v<JoystickDataCache>);
// static_assert(std::is_trivial_v<JoystickDataCache>);
static std::atomic_bool gShutdown{false};
struct FRCDriverStation {
~FRCDriverStation() { gShutdown = true; }
wpi::EventVector newDataEvents;
wpi::mutex cacheMutex;
};
} // namespace
void JoystickDataCache::Update() {
for (int i = 0; i < kJoystickPorts; i++) {
SimDriverStationData->GetJoystickAxes(i, &axes[i]);
SimDriverStationData->GetJoystickPOVs(i, &povs[i]);
SimDriverStationData->GetJoystickButtons(i, &buttons[i]);
}
allianceStation = SimDriverStationData->allianceStationId;
matchTime = SimDriverStationData->matchTime;
}
#define CHECK_JOYSTICK_NUMBER(stickNum) \
if ((stickNum) < 0 || (stickNum) >= HAL_kMaxJoysticks) \
return PARAMETER_OUT_OF_RANGE
static HAL_ControlWord newestControlWord;
static JoystickDataCache caches[3];
static JoystickDataCache* currentRead = &caches[0];
static JoystickDataCache* currentCache = &caches[1];
static JoystickDataCache* cacheToUpdate = &caches[2];
static ::FRCDriverStation* driverStation;
namespace hal::init {
void InitializeDriverStation() {
static wpi::condition_variable nddaC;
newDSDataAvailableCond = &nddaC;
static FRCDriverStation ds;
driverStation = &ds;
}
} // namespace hal::init
using namespace hal;
extern "C" {
void HALSIM_SetSendError(HALSIM_SendErrorHandler handler) {
@@ -122,39 +168,67 @@ int32_t HAL_SendConsoleLine(const char* line) {
}
int32_t HAL_GetControlWord(HAL_ControlWord* controlWord) {
std::memset(controlWord, 0, sizeof(HAL_ControlWord));
controlWord->enabled = SimDriverStationData->enabled;
controlWord->autonomous = SimDriverStationData->autonomous;
controlWord->test = SimDriverStationData->test;
controlWord->eStop = SimDriverStationData->eStop;
controlWord->fmsAttached = SimDriverStationData->fmsAttached;
controlWord->dsAttached = SimDriverStationData->dsAttached;
if (gShutdown) {
return INCOMPATIBLE_STATE;
}
std::scoped_lock lock{driverStation->cacheMutex};
*controlWord = newestControlWord;
return 0;
}
HAL_AllianceStationID HAL_GetAllianceStation(int32_t* status) {
*status = 0;
return SimDriverStationData->allianceStationId;
if (gShutdown) {
return HAL_AllianceStationID_kRed1;
}
std::scoped_lock lock{driverStation->cacheMutex};
return currentRead->allianceStation;
}
int32_t HAL_GetJoystickAxes(int32_t joystickNum, HAL_JoystickAxes* axes) {
SimDriverStationData->GetJoystickAxes(joystickNum, axes);
if (gShutdown) {
return INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
*axes = currentRead->axes[joystickNum];
return 0;
}
int32_t HAL_GetJoystickPOVs(int32_t joystickNum, HAL_JoystickPOVs* povs) {
SimDriverStationData->GetJoystickPOVs(joystickNum, povs);
if (gShutdown) {
return INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
*povs = currentRead->povs[joystickNum];
return 0;
}
int32_t HAL_GetJoystickButtons(int32_t joystickNum,
HAL_JoystickButtons* buttons) {
SimDriverStationData->GetJoystickButtons(joystickNum, buttons);
if (gShutdown) {
return INCOMPATIBLE_STATE;
}
CHECK_JOYSTICK_NUMBER(joystickNum);
std::scoped_lock lock{driverStation->cacheMutex};
*buttons = currentRead->buttons[joystickNum];
return 0;
}
void HAL_GetAllJoystickData(HAL_JoystickAxes* axes, HAL_JoystickPOVs* povs,
HAL_JoystickButtons* buttons) {
if (gShutdown) {
return;
}
std::scoped_lock lock{driverStation->cacheMutex};
std::memcpy(axes, currentRead->axes, sizeof(currentRead->axes));
std::memcpy(povs, currentRead->povs, sizeof(currentRead->povs));
std::memcpy(buttons, currentRead->buttons, sizeof(currentRead->buttons));
}
int32_t HAL_GetJoystickDescriptor(int32_t joystickNum,
HAL_JoystickDescriptor* desc) {
CHECK_JOYSTICK_NUMBER(joystickNum);
SimDriverStationData->GetJoystickDescriptor(joystickNum, desc);
return 0;
}
@@ -196,7 +270,11 @@ int32_t HAL_SetJoystickOutputs(int32_t joystickNum, int64_t outputs,
}
double HAL_GetMatchTime(int32_t* status) {
return SimDriverStationData->matchTime;
if (gShutdown) {
return 0;
}
std::scoped_lock lock{driverStation->cacheMutex};
return currentRead->matchTime;
}
int32_t HAL_GetMatchInfo(HAL_MatchInfo* info) {
@@ -224,103 +302,67 @@ void HAL_ObserveUserProgramTest(void) {
// TODO
}
static int& GetThreadLocalLastCount() {
// There is a rollover error condition here. At Packet# = n * (uintmax), this
// will return false when instead it should return true. However, this at a
// 20ms rate occurs once every 2.7 years of DS connected runtime, so not
// worth the cycles to check.
thread_local int lastCount{0};
return lastCount;
void HAL_RefreshDSData(void) {
if (gShutdown) {
return;
}
HAL_ControlWord controlWord;
std::memset(&controlWord, 0, sizeof(controlWord));
controlWord.enabled = SimDriverStationData->enabled;
controlWord.autonomous = SimDriverStationData->autonomous;
controlWord.test = SimDriverStationData->test;
controlWord.eStop = SimDriverStationData->eStop;
controlWord.fmsAttached = SimDriverStationData->fmsAttached;
controlWord.dsAttached = SimDriverStationData->dsAttached;
std::scoped_lock lock{driverStation->cacheMutex};
if (currentCache->updated) {
std::swap(currentCache, currentRead);
currentCache->updated = false;
}
newestControlWord = controlWord;
}
HAL_Bool HAL_IsNewControlData(void) {
std::scoped_lock lock(newDSDataAvailableMutex);
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount == currentCount) {
return false;
void HAL_ProvideNewDataEventHandle(WPI_EventHandle handle) {
if (gShutdown) {
return;
}
lastCount = currentCount;
return true;
}
void HAL_WaitForDSData(void) {
HAL_WaitForDSDataTimeout(0);
}
HAL_Bool HAL_WaitForDSDataTimeout(double timeout) {
std::unique_lock lock(newDSDataAvailableMutex);
int& lastCount = GetThreadLocalLastCount();
int currentCount = newDSDataAvailableCounter;
if (lastCount != currentCount) {
lastCount = currentCount;
return true;
}
if (isFinalized.load()) {
return false;
}
auto timeoutTime =
std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
while (newDSDataAvailableCounter == currentCount) {
if (timeout > 0) {
auto timedOut = newDSDataAvailableCond->wait_until(lock, timeoutTime);
if (timedOut == std::cv_status::timeout) {
return false;
}
} else {
newDSDataAvailableCond->wait(lock);
}
}
lastCount = newDSDataAvailableCounter;
return true;
}
// Constant number to be used for our occur handle
constexpr int32_t refNumber = 42;
static int32_t newDataOccur(uint32_t refNum) {
// Since we could get other values, require our specific handle
// to signal our threads
if (refNum != refNumber) {
return 0;
}
SimDriverStationData->CallNewDataCallbacks();
std::scoped_lock lock(newDSDataAvailableMutex);
// Nofify all threads
newDSDataAvailableCounter++;
newDSDataAvailableCond->notify_all();
return 0;
}
void HAL_InitializeDriverStation(void) {
hal::init::CheckInit();
static std::atomic_bool initialized{false};
static wpi::mutex initializeMutex;
// Initial check, as if it's true initialization has finished
if (initialized) {
return;
}
std::scoped_lock lock(initializeMutex);
// Second check in case another thread was waiting
if (initialized) {
return;
}
SimDriverStationData->ResetData();
std::atexit([]() {
isFinalized.store(true);
HAL_ReleaseDSMutex();
});
initialized = true;
driverStation->newDataEvents.Add(handle);
}
void HAL_ReleaseDSMutex(void) {
newDataOccur(refNumber);
void HAL_RemoveNewDataEventHandle(WPI_EventHandle handle) {
if (gShutdown) {
return;
}
driverStation->newDataEvents.Remove(handle);
}
HAL_Bool HAL_GetOutputsEnabled(void) {
if (gShutdown) {
return false;
}
std::scoped_lock lock{driverStation->cacheMutex};
return newestControlWord.enabled && newestControlWord.dsAttached;
}
} // extern "C"
namespace hal {
void NewDriverStationData() {
if (gShutdown) {
return;
}
cacheToUpdate->Update();
{
std::scoped_lock lock{driverStation->cacheMutex};
std::swap(currentCache, cacheToUpdate);
currentCache->updated = true;
}
driverStation->newDataEvents.Wakeup();
SimDriverStationData->CallNewDataCallbacks();
}
void InitializeDriverStation() {
SimDriverStationData->ResetData();
}
} // namespace hal

View File

@@ -57,6 +57,10 @@ static std::vector<std::pair<void*, void (*)(void*)>> gOnShutdown;
static SimPeriodicCallbackRegistry gSimPeriodicBefore;
static SimPeriodicCallbackRegistry gSimPeriodicAfter;
namespace hal {
void InitializeDriverStation();
} // namespace hal
namespace hal::init {
void InitializeHAL() {
InitializeAccelerometerData();
@@ -335,7 +339,7 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
hal::init::HAL_IsInitialized.store(true);
hal::RestartTiming();
HAL_InitializeDriverStation();
hal::InitializeDriverStation();
initialized = true;

View File

@@ -216,8 +216,12 @@ void DriverStationData::CallNewDataCallbacks() {
m_newDataCallbacks(&empty);
}
namespace hal {
void NewDriverStationData();
} // namespace hal
void DriverStationData::NotifyNewData() {
HAL_ReleaseDSMutex();
hal::NewDriverStationData();
}
void DriverStationData::SetJoystickButton(int32_t stick, int32_t button,

View File

@@ -61,6 +61,9 @@ TEST(DriverStationTest, Joystick) {
HALSIM_SetJoystickPOVs(joystickUnderTest, &set_povs);
HALSIM_SetJoystickButtons(joystickUnderTest, &set_buttons);
HALSIM_NotifyDriverStationNewData();
HAL_RefreshDSData();
// Check the set values
HAL_GetJoystickAxes(joystickUnderTest, &axes);
HAL_GetJoystickPOVs(joystickUnderTest, &povs);
@@ -89,6 +92,9 @@ TEST(DriverStationTest, Joystick) {
// Reset
HALSIM_ResetDriverStationData();
HALSIM_NotifyDriverStationNewData();
HAL_RefreshDSData();
for (int joystickNum = 0; joystickNum < 6; ++joystickNum) {
HAL_GetJoystickAxes(joystickNum, &axes);
HAL_GetJoystickPOVs(joystickNum, &povs);

View File

@@ -53,12 +53,16 @@ dependencies {
implementation project(':wpilibNewCommands')
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {
onlyIf { false }
}
def simProjects = ['halsim_gui']
deploy {
targets {
roborio(RemoteTarget) {
directory = '/home/admin'
directory = '/home/lvuser'
maxChannels = 4
locations {
ssh(SshDeployLocation) {
@@ -78,7 +82,8 @@ deploy {
artifacts {
all {
predeploy << { ctx ->
ctx.execute('/usr/local/frc/bin/frcKillRobot.sh -t')
ctx.execute('. /etc/profile.d/natinst-path.sh; /usr/local/frc/bin/frcKillRobot.sh -t 2> /dev/null')
ctx.execute("sed -i -e 's/\"exec /\"/' /usr/local/frc/bin/frcRunRobot.sh")
}
postdeploy << { ctx ->
ctx.execute("sync")
@@ -92,6 +97,9 @@ deploy {
excludes.add('**/*.so.debug')
excludes.add('**/*.so.*.debug')
postdeploy << { ctx ->
ctx.execute("echo '/home/lvuser/myRobotCpp' > /home/lvuser/robotCommand")
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
ctx.execute("setcap cap_sys_nice+eip \"/home/lvuser/myRobotCpp\"")
ctx.execute('chmod +x myRobotCpp')
}
}
@@ -99,18 +107,28 @@ deploy {
myRobotCppStatic(NativeExecutableArtifact) {
libraryDirectory = '/usr/local/frc/third-party/lib'
postdeploy << { ctx ->
ctx.execute("echo '/home/lvuser/myRobotCppStatic' > /home/lvuser/robotCommand")
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
ctx.execute("setcap cap_sys_nice+eip \"/home/lvuser/myRobotCppStatic\"")
ctx.execute('chmod +x myRobotCppStatic')
}
}
myRobotCppJava(NativeExecutableArtifact) {
libraryDirectory = '/usr/local/frc/third-party/lib'
def excludes = getLibraryFilter().getExcludes()
excludes.add('**/*.so.debug')
excludes.add('**/*.so.*.debug')
}
jre(WPIJREArtifact) {
}
myRobotJava(JavaArtifact) {
jarTask = shadowJar
postdeploy << { ctx ->
ctx.execute("echo '/usr/local/frc/JRE/bin/java -XX:+UseConcMarkSweepGC -Djava.library.path=/usr/local/frc/third-party/lib -Djava.lang.invoke.stringConcat=BC_SB -jar /home/admin/myRobot-all.jar' > /home/admin/myRobotJavaRun")
ctx.execute("chmod +x /home/admin/myRobotJavaRun; chown lvuser /home/admin/myRobotJavaRun")
ctx.execute("echo '/usr/local/frc/JRE/bin/java -XX:+UseG1GC -XX:MaxGCPauseMillis=1 -XX:GCTimeRatio=1 -Djava.library.path=/usr/local/frc/third-party/lib -Djava.lang.invoke.stringConcat=BC_SB -jar /home/lvuser/myRobot-all.jar' > /home/lvuser/robotCommand")
ctx.execute("chmod +x /home/lvuser/robotCommand; chown lvuser /home/lvuser/robotCommand")
}
}
}
@@ -122,7 +140,7 @@ tasks.register('deployJava') {
try {
dependsOn tasks.named('deployjreroborio')
dependsOn tasks.named('deploymyRobotJavaroborio')
dependsOn tasks.named('deploymyRobotCpproborio') // Deploying shared C++ is how to get the Java shared libraries.
dependsOn tasks.named('deploymyRobotCppJavaroborio') // Deploying shared C++ is how to get the Java shared libraries.
} catch (ignored) {
}
}
@@ -161,6 +179,7 @@ model {
if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
if (binary.buildType.name == 'debug') {
deploy.targets.roborio.artifacts.myRobotCpp.binary = binary
deploy.targets.roborio.artifacts.myRobotCppJava.binary = binary
}
}
lib project: ':wpilibNewCommands', library: 'wpilibNewCommands', linkage: 'shared'

View File

@@ -35,6 +35,7 @@ set_property(TARGET ntcore PROPERTY FOLDER "libraries")
install(TARGETS ntcore EXPORT ntcore DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/ntcore")
install(DIRECTORY ${WPILIB_BINARY_DIR}/ntcore/generated/main/native/include/ DESTINATION "${include_dest}/ntcore")
if (WITH_FLAT_INSTALL)
set (ntcore_config_dir ${wpilib_dest})

View File

@@ -51,12 +51,10 @@ void bench() {
// set up instances
auto client = nt::CreateInstance();
auto server = nt::CreateInstance();
nt::SetNetworkIdentity(server, "server");
nt::SetNetworkIdentity(client, "client");
// connect client and server
nt::StartServer(server, "bench.json", "127.0.0.1", 0, 10000);
nt::StartClient4(client);
nt::StartClient4(client, "client");
nt::SetServer(client, "127.0.0.1", 10000);
using namespace std::chrono_literals;

View File

@@ -5,11 +5,15 @@
package edu.wpi.first.networktables;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.concurrent.Event;
import edu.wpi.first.util.datalog.DataLog;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -30,17 +34,35 @@ import java.util.function.Consumer;
* kept to the NetworkTableInstance returned by this function to keep it from being garbage
* collected.
*/
@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class NetworkTableInstance implements AutoCloseable {
/**
* Client/server mode flag values (as returned by {@link #getNetworkMode()}). This is a bitmask.
*/
public static final int kNetModeNone = 0x00;
/** Client/server mode flag values (as returned by {@link #getNetworkMode()}). */
public enum NetworkMode {
/** Running in server mode. */
kServer(0x01),
public static final int kNetModeServer = 0x01;
public static final int kNetModeClient3 = 0x02;
public static final int kNetModeClient4 = 0x04;
public static final int kNetModeStarting = 0x08;
public static final int kNetModeLocal = 0x10;
/** Running in NT3 client mode. */
kClient3(0x02),
/** Running in NT4 client mode. */
kClient4(0x04),
/** Currently starting up (either client or server). */
kStarting(0x08),
/** Running in local-only mode. */
kLocal(0x10);
private final int value;
NetworkMode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/** The default port that network tables operates on for NT3. */
public static final int kDefaultPort3 = 1735;
@@ -62,7 +84,9 @@ public final class NetworkTableInstance implements AutoCloseable {
@Override
public synchronized void close() {
if (m_owned && m_handle != 0) {
m_listeners.close();
NetworkTablesJNI.destroyInstance(m_handle);
m_handle = 0;
}
}
@@ -350,123 +374,350 @@ public final class NetworkTableInstance implements AutoCloseable {
* Callback Creation Functions
*/
private final ReentrantLock m_connectionListenerLock = new ReentrantLock();
private final Map<Integer, Consumer<ConnectionNotification>> m_connectionListeners =
new HashMap<>();
private int m_connectionListenerPoller;
private static class ListenerStorage implements AutoCloseable {
private final ReentrantLock m_lock = new ReentrantLock();
private final Map<Integer, Consumer<NetworkTableEvent>> m_listeners = new HashMap<>();
private Thread m_thread;
private int m_poller;
private boolean m_waitQueue;
private final Event m_waitQueueEvent = new Event();
private final Condition m_waitQueueCond = m_lock.newCondition();
private final NetworkTableInstance m_inst;
@SuppressWarnings("PMD.AvoidCatchingThrowable")
private void startConnectionListenerThread() {
var connectionListenerThread =
new Thread(
() -> {
boolean wasInterrupted = false;
while (!Thread.interrupted()) {
try {
WPIUtilJNI.waitForObject(m_connectionListenerPoller);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
// don't try to destroy poller, as its handle is likely no longer valid
wasInterrupted = true;
break;
}
ConnectionNotification[] events =
NetworkTablesJNI.readConnectionListenerQueue(this, m_connectionListenerPoller);
for (ConnectionNotification event : events) {
Consumer<ConnectionNotification> listener;
m_connectionListenerLock.lock();
ListenerStorage(NetworkTableInstance inst) {
m_inst = inst;
}
int add(
String[] prefixes,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
m_lock.lock();
try {
if (m_poller == 0) {
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
startThread();
}
int h = NetworkTablesJNI.addListener(m_poller, prefixes, eventKinds);
m_listeners.put(h, listener);
return h;
} finally {
m_lock.unlock();
}
}
int add(
int handle,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
m_lock.lock();
try {
if (m_poller == 0) {
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
startThread();
}
int h = NetworkTablesJNI.addListener(m_poller, handle, eventKinds);
m_listeners.put(h, listener);
return h;
} finally {
m_lock.unlock();
}
}
int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) {
m_lock.lock();
try {
if (m_poller == 0) {
m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle());
startThread();
}
int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel);
m_listeners.put(h, listener);
return h;
} finally {
m_lock.unlock();
}
}
void remove(int listener) {
m_lock.lock();
try {
m_listeners.remove(listener);
} finally {
m_lock.unlock();
}
NetworkTablesJNI.removeListener(listener);
}
@Override
public void close() {
if (m_poller != 0) {
NetworkTablesJNI.destroyListenerPoller(m_poller);
}
m_poller = 0;
}
private void startThread() {
m_thread =
new Thread(
() -> {
boolean wasInterrupted = false;
int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() };
while (!Thread.interrupted()) {
try {
listener = m_connectionListeners.get(event.listener);
} finally {
m_connectionListenerLock.unlock();
}
if (listener != null) {
WPIUtilJNI.waitForObjects(handles);
} catch (InterruptedException ex) {
m_lock.lock();
try {
listener.accept(event);
} catch (Throwable throwable) {
System.err.println(
"Unhandled exception during connection listener callback: "
+ throwable.toString());
throwable.printStackTrace();
if (m_waitQueue) {
m_waitQueue = false;
m_waitQueueCond.signalAll();
}
} finally {
m_lock.unlock();
}
Thread.currentThread().interrupt();
// don't try to destroy poller, as its handle is likely no longer valid
wasInterrupted = true;
break;
}
for (NetworkTableEvent event :
NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) {
Consumer<NetworkTableEvent> listener;
m_lock.lock();
try {
listener = m_listeners.get(event.listener);
} finally {
m_lock.unlock();
}
if (listener != null) {
try {
listener.accept(event);
} catch (Throwable throwable) {
System.err.println(
"Unhandled exception during listener callback: "
+ throwable.toString());
throwable.printStackTrace();
}
}
}
m_lock.lock();
try {
if (m_waitQueue) {
m_waitQueue = false;
m_waitQueueCond.signalAll();
}
} finally {
m_lock.unlock();
}
}
}
m_connectionListenerLock.lock();
try {
if (!wasInterrupted) {
NetworkTablesJNI.destroyConnectionListenerPoller(m_connectionListenerPoller);
m_lock.lock();
try {
if (!wasInterrupted) {
NetworkTablesJNI.destroyListenerPoller(m_poller);
}
m_poller = 0;
} finally {
m_lock.unlock();
}
m_connectionListenerPoller = 0;
} finally {
m_connectionListenerLock.unlock();
}
},
"NTConnectionListener");
connectionListenerThread.setDaemon(true);
connectionListenerThread.start();
}
},
"NTListener");
m_thread.setDaemon(true);
m_thread.start();
}
/**
* Add a connection listener.
*
* @param listener Listener to add
* @param immediateNotify Notify listener of all existing connections
* @return Listener handle
*/
public int addConnectionListener(
Consumer<ConnectionNotification> listener, boolean immediateNotify) {
m_connectionListenerLock.lock();
try {
if (m_connectionListenerPoller == 0) {
m_connectionListenerPoller = NetworkTablesJNI.createConnectionListenerPoller(m_handle);
startConnectionListenerThread();
boolean waitForQueue(double timeout) {
m_lock.lock();
try {
if (m_poller != 0) {
m_waitQueue = true;
m_waitQueueEvent.set();
while (m_waitQueue) {
try {
if (timeout < 0) {
m_waitQueueCond.await();
} else {
return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
return true;
}
}
}
} finally {
m_lock.unlock();
}
int handle =
NetworkTablesJNI.addPolledConnectionListener(m_connectionListenerPoller, immediateNotify);
m_connectionListeners.put(handle, listener);
return handle;
} finally {
m_connectionListenerLock.unlock();
return true;
}
}
private ListenerStorage m_listeners = new ListenerStorage(this);
/**
* Remove a connection listener.
*
* @param listener Listener handle to remove
*/
public void removeConnectionListener(int listener) {
m_connectionListenerLock.lock();
try {
m_connectionListeners.remove(listener);
} finally {
m_connectionListenerLock.unlock();
public void removeListener(int listener) {
m_listeners.remove(listener);
}
/**
* Wait for the listener queue to be empty. This is primarily useful for deterministic
* testing. This blocks until either the listener queue is empty (e.g. there are no
* more events that need to be passed along to callbacks or poll queues) or the timeout expires.
*
* @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to
* block indefinitely
* @return False if timed out, otherwise true.
*/
public boolean waitForListenerQueue(double timeout) {
return m_listeners.waitForQueue(timeout);
}
/**
* Add a connection listener. The callback function is called asynchronously on a separate
* thread, so it's important to use synchronization or atomics when accessing any shared state
* from the callback function.
*
* @param immediateNotify Notify listener of all existing connections
* @param listener Listener to add
* @return Listener handle
*/
public int addConnectionListener(
boolean immediateNotify, Consumer<NetworkTableEvent> listener) {
EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection);
if (immediateNotify) {
eventKinds.add(NetworkTableEvent.Kind.kImmediate);
}
NetworkTablesJNI.removeConnectionListener(listener);
return m_listeners.add(m_handle, eventKinds, listener);
}
/**
* Add a listener for changes on a particular topic. The callback function is called
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
* accessing any shared state from the callback function.
*
* <p>This creates a corresponding internal subscriber with the lifetime of the
* listener.
*
* @param topic Topic
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
Topic topic,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (topic.getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("topic is not from this instance");
}
return m_listeners.add(topic.getHandle(), eventKinds, listener);
}
/**
* Add a listener for changes on a subscriber. The callback function is called
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
* accessing any shared state from the callback function. This does NOT keep the subscriber
* active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
Subscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (subscriber.getTopic().getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("subscriber is not from this instance");
}
return m_listeners.add(subscriber.getHandle(), eventKinds, listener);
}
/**
* Add a listener for changes on a subscriber. The callback function is called
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
* accessing any shared state from the callback function. This does NOT keep the subscriber
* active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
MultiSubscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (subscriber.getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("subscriber is not from this instance");
}
return m_listeners.add(subscriber.getHandle(), eventKinds, listener);
}
/**
* Add a listener for changes on an entry. The callback function is called
* asynchronously on a separate thread, so it's important to use synchronization or atomics when
* accessing any shared state from the callback function.
*
* @param entry Entry
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
NetworkTableEntry entry,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
if (entry.getTopic().getInstance().getHandle() != m_handle) {
throw new IllegalArgumentException("entry is not from this instance");
}
return m_listeners.add(entry.getHandle(), eventKinds, listener);
}
/**
* Add a listener for changes to topics with names that start with any of the given
* prefixes. The callback function is called asynchronously on a separate thread, so it's
* important to use synchronization or atomics when accessing any shared state from the callback
* function.
*
* <p>This creates a corresponding internal subscriber with the lifetime of the
* listener.
*
* @param prefixes Topic name string prefixes
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener handle
*/
public int addListener(
String[] prefixes,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
return m_listeners.add(prefixes, eventKinds, listener);
}
/*
* Client/Server Functions
*/
/**
* Set the network identity of this node. This is the name used during the initial connection
* handshake, and is visible through ConnectionInfo on the remote node.
*
* @param name identity to advertise
*/
public void setNetworkIdentity(String name) {
NetworkTablesJNI.setNetworkIdentity(m_handle, name);
}
/**
* Get the current network mode.
*
* @return Bitmask of NetworkMode.
* @return Enum set of NetworkMode.
*/
public int getNetworkMode() {
return NetworkTablesJNI.getNetworkMode(m_handle);
public EnumSet<NetworkMode> getNetworkMode() {
int flags = NetworkTablesJNI.getNetworkMode(m_handle);
EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class);
for (NetworkMode mode : NetworkMode.values()) {
if ((flags & mode.getValue()) != 0) {
rv.add(mode);
}
}
return rv;
}
/**
@@ -478,8 +729,8 @@ public final class NetworkTableInstance implements AutoCloseable {
}
/**
* Stops local-only operation. startServer or startClient can be called after this call to start a
* server or client.
* Stops local-only operation. startServer or startClient can be called after this call to start
* a server or client.
*/
public void stopLocal() {
NetworkTablesJNI.stopLocal(m_handle);
@@ -541,14 +792,22 @@ public final class NetworkTableInstance implements AutoCloseable {
NetworkTablesJNI.stopServer(m_handle);
}
/** Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port. */
public void startClient3() {
NetworkTablesJNI.startClient3(m_handle);
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient3(String identity) {
NetworkTablesJNI.startClient3(m_handle, identity);
}
/** Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port. */
public void startClient4() {
NetworkTablesJNI.startClient4(m_handle);
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient4(String identity) {
NetworkTablesJNI.startClient4(m_handle, identity);
}
/** Stops the client if it is running. */
@@ -602,8 +861,8 @@ public final class NetworkTableInstance implements AutoCloseable {
}
/**
* Sets server addresses and ports for client (without restarting client). The client will attempt
* to connect to each server in round robin fashion.
* Sets server addresses and ports for client (without restarting client). The client will
* attempt to connect to each server in round robin fashion.
*
* @param serverNames array of server names
* @param ports array of port numbers (0=default)
@@ -666,8 +925,8 @@ public final class NetworkTableInstance implements AutoCloseable {
/**
* Flushes all updated values immediately to the network. Note: This is rate-limited to protect
* the network from flooding. This is primarily useful for synchronizing network updates with user
* code.
* the network from flooding. This is primarily useful for synchronizing network updates with
* user code.
*/
public void flush() {
NetworkTablesJNI.flush(m_handle);
@@ -736,98 +995,19 @@ public final class NetworkTableInstance implements AutoCloseable {
NetworkTablesJNI.stopConnectionDataLog(logger);
}
private final ReentrantLock m_loggerLock = new ReentrantLock();
private final Map<Integer, Consumer<LogMessage>> m_loggers = new HashMap<>();
private int m_loggerPoller;
@SuppressWarnings("PMD.AvoidCatchingThrowable")
private void startLogThread() {
var loggerThread =
new Thread(
() -> {
boolean wasInterrupted = false;
while (!Thread.interrupted()) {
try {
WPIUtilJNI.waitForObject(m_loggerPoller);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
// don't try to destroy poller, as its handle is likely no longer valid
wasInterrupted = true;
break;
}
LogMessage[] events = NetworkTablesJNI.readLoggerQueue(this, m_loggerPoller);
for (LogMessage event : events) {
Consumer<LogMessage> logger;
m_loggerLock.lock();
try {
logger = m_loggers.get(event.logger);
} finally {
m_loggerLock.unlock();
}
if (logger != null) {
try {
logger.accept(event);
} catch (Throwable throwable) {
System.err.println(
"Unhandled exception during logger callback: " + throwable.toString());
throwable.printStackTrace();
}
}
}
}
m_loggerLock.lock();
try {
if (!wasInterrupted) {
NetworkTablesJNI.destroyLoggerPoller(m_loggerPoller);
}
} finally {
m_loggerLock.unlock();
}
},
"NTLogger");
loggerThread.setDaemon(true);
loggerThread.start();
}
/**
* Add logger callback function. By default, log messages are sent to stderr; this function sends
* log messages with the specified levels to the provided callback function instead. The callback
* function will only be called for log messages with level greater than or equal to minLevel and
* less than or equal to maxLevel; messages outside this range will be silently ignored.
*
* @param func log callback function
* @param minLevel minimum log level
* @param maxLevel maximum log level
* @return Logger handle
* @param func callback function
* @return Listener handle
*/
public int addLogger(Consumer<LogMessage> func, int minLevel, int maxLevel) {
m_loggerLock.lock();
try {
if (m_loggerPoller == 0) {
m_loggerPoller = NetworkTablesJNI.createLoggerPoller(m_handle);
startLogThread();
}
int handle = NetworkTablesJNI.addPolledLogger(m_loggerPoller, minLevel, maxLevel);
m_loggers.put(handle, func);
return handle;
} finally {
m_loggerLock.unlock();
}
}
/**
* Remove a logger.
*
* @param logger Logger handle to remove
*/
public void removeLogger(int logger) {
m_loggerLock.lock();
try {
m_loggers.remove(logger);
} finally {
m_loggerLock.unlock();
}
NetworkTablesJNI.removeLogger(logger);
public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) {
return m_listeners.addLogger(minLevel, maxLevel, func);
}
@Override
@@ -848,5 +1028,5 @@ public final class NetworkTableInstance implements AutoCloseable {
}
private boolean m_owned;
private final int m_handle;
private int m_handle;
}

View File

@@ -7,6 +7,7 @@ package edu.wpi.first.networktables;
import edu.wpi.first.util.RuntimeLoader;
import edu.wpi.first.util.datalog.DataLog;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicBoolean;
public final class NetworkTablesJNI {
@@ -199,42 +200,34 @@ public final class NetworkTablesJNI {
public static native TopicInfo getTopicInfo(NetworkTableInstance inst, int topic);
public static native int createTopicListenerPoller(int inst);
public static native int createListenerPoller(int inst);
public static native void destroyTopicListenerPoller(int poller);
public static native void destroyListenerPoller(int poller);
public static native int addPolledTopicListener(int poller, String[] prefixes, int flags);
private static int kindsToMask(EnumSet<NetworkTableEvent.Kind> kinds) {
int mask = 0;
for (NetworkTableEvent.Kind kind : kinds) {
mask |= kind.getValue();
}
return mask;
}
public static native int addPolledTopicListener(int poller, int handle, int flags);
public static int addListener(int poller, String[] prefixes, EnumSet<NetworkTableEvent.Kind> kinds) {
return addListener(poller, prefixes, kindsToMask(kinds));
}
public static native TopicNotification[] readTopicListenerQueue(
public static int addListener(int poller, int handle, EnumSet<NetworkTableEvent.Kind> kinds) {
return addListener(poller, handle, kindsToMask(kinds));
}
public static native int addListener(int poller, String[] prefixes, int mask);
public static native int addListener(int poller, int handle, int mask);
public static native NetworkTableEvent[] readListenerQueue(
NetworkTableInstance inst, int poller);
public static native void removeTopicListener(int topicListener);
public static native int createValueListenerPoller(int inst);
public static native void destroyValueListenerPoller(int poller);
public static native int addPolledValueListener(int poller, int subentry, int flags);
public static native ValueNotification[] readValueListenerQueue(
NetworkTableInstance inst, int poller);
public static native void removeValueListener(int valueListener);
public static native int createConnectionListenerPoller(int inst);
public static native void destroyConnectionListenerPoller(int poller);
public static native int addPolledConnectionListener(int poller, boolean immediateNotify);
public static native ConnectionNotification[] readConnectionListenerQueue(
NetworkTableInstance inst, int poller);
public static native void removeConnectionListener(int connListener);
public static native void setNetworkIdentity(int inst, String name);
public static native void removeListener(int listener);
public static native int getNetworkMode(int inst);
@@ -247,9 +240,9 @@ public final class NetworkTablesJNI {
public static native void stopServer(int inst);
public static native void startClient3(int inst);
public static native void startClient3(int inst, String identity);
public static native void startClient4(int inst);
public static native void startClient4(int inst, String identity);
public static native void stopClient(int inst);
@@ -289,13 +282,5 @@ public final class NetworkTablesJNI {
public static native void stopConnectionDataLog(int logger);
public static native int createLoggerPoller(int inst);
public static native void destroyLoggerPoller(int poller);
public static native int addPolledLogger(int poller, int minLevel, int maxLevel);
public static native LogMessage[] readLoggerQueue(NetworkTableInstance inst, int poller);
public static native void removeLogger(int logger);
public static native int addLogger(int poller, int minLevel, int maxLevel);
}

View File

@@ -9,7 +9,7 @@ package edu.wpi.first.networktables;
public final class ConnectionInfo {
/**
* The remote identifier (as set on the remote node by {@link
* NetworkTableInstance#setNetworkIdentity(String)}).
* NetworkTableInstance#startClient4(String)}).
*/
public final String remote_id;

View File

@@ -1,141 +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.
package edu.wpi.first.networktables;
import edu.wpi.first.util.WPIUtilJNI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
/** Connection listener. This calls back to a callback function when a connection change occurs. */
public final class ConnectionListener implements AutoCloseable {
/**
* Create a listener for connection changes.
*
* @param inst Instance
* @param immediateNotify if notification should be immediately created for existing connections
* @param listener Listener function
*/
public ConnectionListener(
NetworkTableInstance inst,
boolean immediateNotify,
Consumer<ConnectionNotification> listener) {
s_lock.lock();
try {
if (s_poller == 0) {
s_inst = inst;
s_poller = NetworkTablesJNI.createConnectionListenerPoller(inst.getHandle());
startThread();
}
m_handle = NetworkTablesJNI.addPolledConnectionListener(s_poller, immediateNotify);
s_listeners.put(m_handle, listener);
} finally {
s_lock.unlock();
}
}
@Override
public synchronized void close() {
if (m_handle != 0) {
s_lock.lock();
try {
s_listeners.remove(m_handle);
} finally {
s_lock.unlock();
}
NetworkTablesJNI.removeConnectionListener(m_handle);
m_handle = 0;
}
}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
public boolean isValid() {
return m_handle != 0;
}
/**
* Gets the native handle.
*
* @return Native handle
*/
public int getHandle() {
return m_handle;
}
private int m_handle;
private static final ReentrantLock s_lock = new ReentrantLock();
private static final Map<Integer, Consumer<ConnectionNotification>> s_listeners = new HashMap<>();
private static Thread s_thread;
private static NetworkTableInstance s_inst;
private static int s_poller;
private static boolean s_waitQueue;
private static final Condition s_waitQueueCond = s_lock.newCondition();
private static void startThread() {
s_thread =
new Thread(
() -> {
boolean wasInterrupted = false;
while (!Thread.interrupted()) {
try {
WPIUtilJNI.waitForObject(s_poller);
} catch (InterruptedException ex) {
s_lock.lock();
try {
if (s_waitQueue) {
s_waitQueue = false;
s_waitQueueCond.signalAll();
continue;
}
} finally {
s_lock.unlock();
}
Thread.currentThread().interrupt();
// don't try to destroy poller, as its handle is likely no longer valid
wasInterrupted = true;
break;
}
for (ConnectionNotification event :
NetworkTablesJNI.readConnectionListenerQueue(s_inst, s_poller)) {
Consumer<ConnectionNotification> listener;
s_lock.lock();
try {
listener = s_listeners.get(event.listener);
} finally {
s_lock.unlock();
}
if (listener != null) {
try {
listener.accept(event);
} catch (Throwable throwable) {
System.err.println(
"Unhandled exception during listener callback: " + throwable.toString());
throwable.printStackTrace();
}
}
}
}
s_lock.lock();
try {
if (!wasInterrupted) {
NetworkTablesJNI.destroyConnectionListenerPoller(s_poller);
}
s_poller = 0;
} finally {
s_lock.unlock();
}
},
"ConnectionListener");
s_thread.setDaemon(true);
s_thread.start();
}
}

View File

@@ -1,78 +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.
package edu.wpi.first.networktables;
/**
* A connection listener. This queues connection notifications. Code using the listener must
* periodically call readQueue() to read the notifications.
*/
public final class ConnectionListenerPoller implements AutoCloseable {
/**
* Construct a connection listener poller.
*
* @param inst Instance
*/
public ConnectionListenerPoller(NetworkTableInstance inst) {
m_inst = inst;
m_handle = NetworkTablesJNI.createConnectionListenerPoller(inst.getHandle());
}
/**
* Create a connection listener.
*
* @param immediateNotify if notification should be immediately created for existing connections
* @return Listener handle
*/
public int add(boolean immediateNotify) {
return NetworkTablesJNI.addPolledConnectionListener(m_handle, immediateNotify);
}
/**
* Remove a connection listener.
*
* @param listener Listener handle
*/
public void remove(int listener) {
NetworkTablesJNI.removeConnectionListener(listener);
}
/**
* Read connection notifications.
*
* @return Connection notifications since the previous call to readQueue()
*/
public ConnectionNotification[] readQueue() {
return NetworkTablesJNI.readConnectionListenerQueue(m_inst, m_handle);
}
@Override
public synchronized void close() {
if (m_handle != 0) {
NetworkTablesJNI.destroyConnectionListenerPoller(m_handle);
}
m_handle = 0;
}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
public boolean isValid() {
return m_handle != 0;
}
/**
* Gets the native handle.
*
* @return Handle
*/
public int getHandle() {
return m_handle;
}
private final NetworkTableInstance m_inst;
private int m_handle;
}

View File

@@ -1,43 +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.
package edu.wpi.first.networktables;
/** NetworkTables Connection notification. */
@SuppressWarnings("MemberName")
public final class ConnectionNotification {
/**
* Handle of listener that was triggered. ConnectionListener.getHandle() or the return value of
* ConnectionListenerPoller.add() can be used to map this to a specific added listener.
*/
public final int listener;
/** True if event is due to connection being established. */
public final boolean connected;
/** Connection information. */
public final ConnectionInfo conn;
/**
* Constructor. This should generally only be used internally to NetworkTables.
*
* @param inst Instance
* @param listener Listener that was triggered
* @param connected Connected if true
* @param conn Connection information
*/
public ConnectionNotification(
NetworkTableInstance inst, int listener, boolean connected, ConnectionInfo conn) {
this.m_inst = inst;
this.listener = listener;
this.connected = connected;
this.conn = conn;
}
private final NetworkTableInstance m_inst;
public NetworkTableInstance getInstance() {
return m_inst;
}
}

View File

@@ -19,9 +19,6 @@ public final class LogMessage {
public static final int kDebug3 = 7;
public static final int kDebug4 = 6;
/** The logger that generated the message. */
public final int logger;
/** Log level of the message. */
public final int level;
@@ -37,26 +34,15 @@ public final class LogMessage {
/**
* Constructor. This should generally only be used internally to NetworkTables.
*
* @param inst Instance
* @param logger Logger
* @param level Log level
* @param filename Filename
* @param line Line number
* @param message Message
*/
public LogMessage(
NetworkTableInstance inst, int logger, int level, String filename, int line, String message) {
this.m_inst = inst;
this.logger = logger;
public LogMessage(int level, String filename, int line, String message) {
this.level = level;
this.filename = filename;
this.line = line;
this.message = message;
}
private final NetworkTableInstance m_inst;
NetworkTableInstance getInstance() {
return m_inst;
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.networktables;
/**
* NetworkTables event.
*
* <p>There are different kinds of events. When creating a listener, a combination of event kinds
* can be listened to by building an EnumSet of NetworkTableEvent.Kind.
*/
@SuppressWarnings("MemberName")
public final class NetworkTableEvent {
public enum Kind {
/**
* Initial listener addition. Set this to receive immediate notification of matches to other
* criteria.
*/
kImmediate(0x0001),
/** Client connected (on server, any client connected). */
kConnected(0x0002),
/** Client disconnected (on server, any client disconnected). */
kDisconnected(0x0004),
/** Any connection event (connect or disconnect). */
kConnection(0x0004 | 0x0002),
/** New topic published. */
kPublish(0x0008),
/** Topic unpublished. */
kUnpublish(0x0010),
/** Topic properties changed. */
kProperties(0x0020),
/** Any topic event (publish, unpublish, or properties changed). */
kTopic(0x0020 | 0x0010 | 0x0008),
/** Topic value updated (via network). */
kValueRemote(0x0040),
/** Topic value updated (local). */
kValueLocal(0x0080),
/** Topic value updated (network or local). */
kValueAll(0x0080 | 0x0040),
/** Log message. */
kLogMessage(0x0100);
private final int value;
Kind(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* Handle of listener that was triggered. The value returned when adding the listener can be used
* to map this to a specific added listener.
*/
public final int listener;
/**
* Determine if event is of a particular kind. For example, kPublish if the topic was not
* previously published. Also indicates the data included with the event:
*
* <ul>
* <li>kConnected or kDisconnected: connInfo
* <li>kPublish, kUnpublish, or kProperties: topicInfo
* <li>kValueRemote, kValueLocal: valueData
* <li>kLogMessage: logMessage
* </ul>
*
* @param kind Kind
* @return True if event matches kind
*/
public boolean is(Kind kind) {
return (m_flags & kind.getValue()) != 0;
}
private final int m_flags;
/** Connection information (for connection events). */
public final ConnectionInfo connInfo;
/** Topic information (for topic events). */
public final TopicInfo topicInfo;
/** Value data (for value events). */
public final ValueEventData valueData;
/** Log message (for log message events). */
public final LogMessage logMessage;
/**
* Constructor. This should generally only be used internally to NetworkTables.
*
* @param inst Instance
* @param listener Listener that was triggered
* @param flags Event flags
* @param connInfo Connection information
* @param topicInfo Topic information
* @param valueData Value data
* @param logMessage Log message
*/
public NetworkTableEvent(
NetworkTableInstance inst,
int listener,
int flags,
ConnectionInfo connInfo,
TopicInfo topicInfo,
ValueEventData valueData,
LogMessage logMessage) {
this.m_inst = inst;
this.listener = listener;
this.m_flags = flags;
this.connInfo = connInfo;
this.topicInfo = topicInfo;
this.valueData = valueData;
this.logMessage = logMessage;
}
/* Network table instance. */
private final NetworkTableInstance m_inst;
/**
* Gets the instance associated with this event.
*
* @return Instance
*/
public NetworkTableInstance getInstance() {
return m_inst;
}
}

View File

@@ -0,0 +1,176 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.networktables;
import java.util.EnumSet;
import java.util.function.Consumer;
/**
* Event listener. This calls back to a callback function when an event matching the specified mask
* occurs. The callback function is called asynchronously on a separate thread, so it's important to
* use synchronization or atomics when accessing any shared state from the callback function.
*/
public final class NetworkTableListener implements AutoCloseable {
/**
* Create a listener for changes to topics with names that start with any of the given prefixes.
* This creates a corresponding internal subscriber with the lifetime of the listener.
*
* @param inst Instance
* @param prefixes Topic name string prefixes
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
NetworkTableInstance inst,
String[] prefixes,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
return new NetworkTableListener(inst, inst.addListener(prefixes, eventKinds, listener));
}
/**
* Create a listener for changes on a particular topic. This creates a corresponding internal
* subscriber with the lifetime of the listener.
*
* @param topic Topic
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
Topic topic,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = topic.getInstance();
return new NetworkTableListener(inst, inst.addListener(topic, eventKinds, listener));
}
/**
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
Subscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = subscriber.getTopic().getInstance();
return new NetworkTableListener(inst, inst.addListener(subscriber, eventKinds, listener));
}
/**
* Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
MultiSubscriber subscriber,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = subscriber.getInstance();
return new NetworkTableListener(inst, inst.addListener(subscriber, eventKinds, listener));
}
/**
* Create a listener for topic changes on an entry.
*
* @param entry Entry
* @param eventKinds set of event kinds to listen to
* @param listener Listener function
* @return Listener
*/
public static NetworkTableListener createListener(
NetworkTableEntry entry,
EnumSet<NetworkTableEvent.Kind> eventKinds,
Consumer<NetworkTableEvent> listener) {
NetworkTableInstance inst = entry.getInstance();
return new NetworkTableListener(inst, inst.addListener(entry, eventKinds, listener));
}
/**
* Create a connection listener.
*
* @param inst instance
* @param immediateNotify notify listener of all existing connections
* @param listener listener function
* @return Listener
*/
public static NetworkTableListener createConnectionListener(
NetworkTableInstance inst, boolean immediateNotify, Consumer<NetworkTableEvent> listener) {
return new NetworkTableListener(inst, inst.addConnectionListener(immediateNotify, listener));
}
/**
* Create a listener for log messages. By default, log messages are sent to stderr; this function
* sends log messages with the specified levels to the provided callback function instead. The
* callback function will only be called for log messages with level greater than or equal to
* minLevel and less than or equal to maxLevel; messages outside this range will be silently
* ignored.
*
* @param inst instance
* @param minLevel minimum log level
* @param maxLevel maximum log level
* @param listener listener function
* @return Listener
*/
public static NetworkTableListener createLogger(
NetworkTableInstance inst, int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) {
return new NetworkTableListener(inst, inst.addLogger(minLevel, maxLevel, listener));
}
@Override
public synchronized void close() {
if (m_handle != 0) {
m_inst.removeListener(m_handle);
m_handle = 0;
}
}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
public boolean isValid() {
return m_handle != 0;
}
/**
* Gets the native handle.
*
* @return Native handle
*/
public int getHandle() {
return m_handle;
}
/**
* Wait for the topic listener queue to be empty. This is primarily useful for deterministic
* testing. This blocks until either the topic listener queue is empty (e.g. there are no more
* events that need to be passed along to callbacks or poll queues) or the timeout expires.
*
* @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to
* block indefinitely
* @return False if timed out, otherwise true.
*/
public boolean waitForQueue(double timeout) {
return m_inst.waitForListenerQueue(timeout);
}
private NetworkTableListener(NetworkTableInstance inst, int handle) {
m_inst = inst;
m_handle = handle;
}
private final NetworkTableInstance m_inst;
private int m_handle;
}

View File

@@ -0,0 +1,157 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.networktables;
import java.util.EnumSet;
/**
* Topic change listener. This queues topic change events matching the specified mask. Code using
* the listener must periodically call readQueue() to read the events.
*/
public final class NetworkTableListenerPoller implements AutoCloseable {
/**
* Construct a topic listener poller.
*
* @param inst Instance
*/
public NetworkTableListenerPoller(NetworkTableInstance inst) {
m_inst = inst;
m_handle = NetworkTablesJNI.createListenerPoller(inst.getHandle());
}
/**
* Start listening to topic changes for topics with names that start with any of the given
* prefixes. This creates a corresponding internal subscriber with the lifetime of the listener.
*
* @param prefixes Topic name string prefixes
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(String[] prefixes, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, prefixes, eventKinds);
}
/**
* Start listening to changes to a particular topic. This creates a corresponding internal
* subscriber with the lifetime of the listener.
*
* @param topic Topic
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(Topic topic, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, topic.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(Subscriber subscriber, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on a subscriber. This does NOT keep the subscriber active.
*
* @param subscriber Subscriber
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(MultiSubscriber subscriber, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventKinds);
}
/**
* Start listening to topic changes on an entry.
*
* @param entry Entry
* @param eventKinds set of event kinds to listen to
* @return Listener handle
*/
public int addListener(NetworkTableEntry entry, EnumSet<NetworkTableEvent.Kind> eventKinds) {
return NetworkTablesJNI.addListener(m_handle, entry.getHandle(), eventKinds);
}
/**
* Add a connection listener. The callback function is called asynchronously on a separate thread,
* so it's important to use synchronization or atomics when accessing any shared state from the
* callback function.
*
* @param immediateNotify notify listener of all existing connections
* @return Listener handle
*/
public int addConnectionListener(boolean immediateNotify) {
EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection);
if (immediateNotify) {
eventKinds.add(NetworkTableEvent.Kind.kImmediate);
}
return NetworkTablesJNI.addListener(m_handle, m_inst.getHandle(), eventKinds);
}
/**
* Add logger callback function. By default, log messages are sent to stderr; this function sends
* log messages with the specified levels to the provided callback function instead. The callback
* function will only be called for log messages with level greater than or equal to minLevel and
* less than or equal to maxLevel; messages outside this range will be silently ignored.
*
* @param minLevel minimum log level
* @param maxLevel maximum log level
* @return Listener handle
*/
public int addLogger(int minLevel, int maxLevel) {
return NetworkTablesJNI.addLogger(m_handle, minLevel, maxLevel);
}
/**
* Remove a listener.
*
* @param listener Listener handle
*/
public void removeListener(int listener) {
NetworkTablesJNI.removeListener(listener);
}
/**
* Read topic notifications.
*
* @return Topic notifications since the previous call to readQueue()
*/
public NetworkTableEvent[] readQueue() {
return NetworkTablesJNI.readListenerQueue(m_inst, m_handle);
}
@Override
public synchronized void close() {
if (m_handle != 0) {
NetworkTablesJNI.destroyListenerPoller(m_handle);
}
m_handle = 0;
}
/**
* Determines if the native handle is valid.
*
* @return True if the native handle is valid, false otherwise.
*/
public boolean isValid() {
return m_handle != 0;
}
/**
* Gets the native handle.
*
* @return Handle
*/
public int getHandle() {
return m_handle;
}
private final NetworkTableInstance m_inst;
private int m_handle;
}

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