Compare commits

...

259 Commits

Author SHA1 Message Date
Peter Johnson
ffbf6a1fa2 [commands] Disable regularly failing unit test (#4824) 2022-12-15 21:53:08 -08:00
Starlight220
fbabd0ef15 [commands] Enhance Command Sendable implementations (#4822) 2022-12-15 18:28:52 -08:00
Thad House
7713f68772 [hal] Use atomic rather then mutex for DS Data updates (#4787)
Using an atomic here means we are never going against a lock that is touchable from user code. That should make reading the DS data from the DS callback even safer.
2022-12-15 18:27:52 -08:00
Starlight220
701995d6cc [examples] Update Command-based starter project (#4778) 2022-12-15 09:40:14 -08:00
Thad House
bf7068ac27 [wpilibc] Add missing PPS implementation for C++ (#4821) 2022-12-15 09:28:42 -08:00
Peter Johnson
aae0f52ca6 [ntcore] NetworkTable: fix visibility of get/set value (#4820) 2022-12-14 19:43:53 -08:00
Thad House
ee02fb7ba7 [hal] Add support for Pulse-Per-Second signal (#4819) 2022-12-14 18:15:34 -08:00
Thad House
518916ba02 [wpilib] Fix DS mode thread event being manual reset accidentally (#4818)
These need to be automatic reset.
2022-12-14 10:17:13 -08:00
Thad House
3997c6635b [hal] Update to new image, use new TCP notify callback and new duty cycle API (#4774) 2022-12-13 22:37:42 -08:00
Michael Leong
cc8675a4e5 [examples] Add comment on how to view elevator sim (NFC) (#4482) 2022-12-13 22:34:00 -08:00
Peter Johnson
fb2c170b6e [ntcore] Simplify local startup (#4803)
The current approach was slightly more efficient but didn't correctly
handle meta-topic publishing for topics published before the server was
started.
2022-12-13 22:31:44 -08:00
shueja-personal
7ba8a9ee1f [wpimath] ProfiledPIDController: Add to SendableRegistry (#4656)
Co-authored-by: Ryan Blue <ryanzblue@gmail.com>
2022-12-13 22:31:06 -08:00
Sriman Achanta
c569d8e523 [wpilib] Joystick.getMagnitude(): use hypot() function (#4816) 2022-12-13 22:29:09 -08:00
bovlb
2a5e89fa97 [apriltag] Improve description of pose coordinates (NFC) (#4810)
The "bottom-left" language appears to be based on [a 2-D diagram  on another page](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/geometry/coordinate-systems.html) and doesn't seem very helpful here.
2022-12-13 22:27:43 -08:00
Drew Williams
cc003c6c38 [apriltag] Fix AprilTagFieldLayout JSON name (#4814) 2022-12-13 22:27:08 -08:00
Sriman Achanta
5522916123 [commands] CommandXBoxController bumper documentation fix (NFC) (#4815) 2022-12-13 22:26:35 -08:00
Peter Johnson
967b30de3a [glass] Fix NT view UpdateClients() bug (#4808)
If UpdateClients() was called in the same update batch as an entry
removal, it could crash in GetEntry() due to a null entry caused by
deletion before a removal erase pass was made.
2022-12-12 23:38:36 -08:00
Tyler Veness
3270d4fc86 [wpimath] Rewrite pose estimator docs (#4807) 2022-12-12 20:30:52 -08:00
Thad House
be39678447 [apriltag] Add test to ensure apriltagjni loads (#4805) 2022-12-12 20:30:25 -08:00
Starlight220
61c75deb2a [commands] Test no-op behavior of scheduling a scheduled command (#4806) 2022-12-12 20:29:47 -08:00
Peter Johnson
a865f48e96 [ntcore] Pass pub/sub options as a unified PubSubOptions struct (#4794)
In Java, PubSubOption is still used for passing options, but this
simplifies C++ use substantially, as it allows aggregate construction.
2022-12-12 19:28:15 -08:00
Ryan Blue
f66a667321 [commands] Fix incorrect Trigger docs (NFC) (#4792) 2022-12-12 15:38:29 -08:00
Peter Johnson
f8d4e9866e [ntcore] Clean up ntcore_test.h (#4804)
The RPC functions are no longer implemented.

Also make header C-safe.
2022-12-12 15:36:54 -08:00
Tyler Veness
7e84ea891f [wpimath] Fix ComputerVisionUtil transform example in parameter docs (NFC) (#4800) 2022-12-11 22:44:08 -08:00
Charlie
da3ec1be10 [wpimath] Change terminology for ArmFeedforward gravity gain (NFC) (#4791) 2022-12-11 22:43:44 -08:00
Ryan Blue
944dd7265d [wpilibc] Add C++ Notifier error handling, update java notifier error message (#4795) 2022-12-11 22:42:22 -08:00
Ryan Blue
6948cea67a [wpiutil] Fix MemoryBuffer initialization (#4797)
Dereferencing an end iterator is undefined behavior and causes a failed assertion when running in Windows debug.
2022-12-11 07:39:04 -08:00
Ryan Blue
a31459bce6 [wpiutil] Fix UnescapeCString overflow when inputSize < 2 (#4796)
Add tests for empty, small, and string without escapes.
2022-12-11 07:36:38 -08:00
ohowe
4a0ad6b48c [wpimath] Rotation2d: Add reference to angleModulus in docs (NFC) (#4786) 2022-12-09 23:35:15 -08:00
Peter Johnson
e6552d272e [ntcore] Remove table multi-subscriber (#4789)
This wasn't well thought out, and leaks horribly in Java.

Reverts part of #4640.
2022-12-09 21:25:29 -08:00
Ryan Blue
bde383f763 [hal] Replace const char* with std::string_view in Driver Station sim functions (#4532) 2022-12-09 13:10:23 -08:00
Ryan Blue
5a52b51443 [hal] Add RobotController.getSerialNumber() (#4783) 2022-12-08 21:58:55 -08:00
Thad House
69a66ec5ec [wpilib] Fix multiple motor safety issues (#4784)
Java was missing the motor safety thread entirely
C++ accidentally used a manual reset event, causing the motor safety thread to spin.
C++ PWMMotorController would not feed the watch kitty.
Both languages would call feed() from the StopMotor call, causing some ping ponging.
2022-12-08 19:47:47 -08:00
sciencewhiz
989c9fb29a [wpimath] Revert Rotation2D change that limits angles (#4781)
Reverts "[wpimath] Constrain Rotation2d range to -pi to pi (#4611)"
This reverts commit d1d458db2b.

This broke multiple teams code in beta. It is also easier to limit the angle externally, then reconstruct a larger angle that got limited. This additionally adds comments to clarify the behavior and retains tests that were added in the reverted commit, and fixes a javadoc comment angle reference.
2022-12-08 18:10:44 -08:00
Peter Johnson
0f5b08ec69 [wpigui] Update imgui to 1.89.1+ (#4780) 2022-12-08 00:24:27 -08:00
Starlight220
fba191099c [examples] AddressableLED: Add unit test (#4779) 2022-12-07 21:47:47 -08:00
Peter Johnson
b390cad095 [wpilibj] Consistently use ErrorMessages.requireNonNullParam (#4776)
Also remove wpilibj version of ErrorMessages and consistently use static import.
2022-12-07 21:46:26 -08:00
Peter Johnson
b9772214d9 [wpilib] Sendable: Don't call setter for getter changes 2022-12-07 21:29:51 -08:00
Peter Johnson
342c375a71 [ntcore] Add subscriber option to exclude single publisher
This enables limited self-exclusion for entries seeing their own changes
or similarly with a pub/sub pair.
2022-12-07 21:29:51 -08:00
Peter Johnson
b0e4053087 [ntcore] Use int for options instead of double
Periodic time is stored in milliseconds.
2022-12-07 21:29:51 -08:00
Peter Johnson
f3e666b7bb [cscore] Convert YUYV and UYVY directly to grayscale (#4777)
This is significantly more efficient than converting by way of BGR.
2022-12-07 10:36:40 -08:00
William Toth
b300518bd1 [hal] Add CAN Stream API to Java through JNI bindings (#4193)
The CAN Stream API allows defining an buffer to receive an
arbitrary set of CAN messages, based on an ID and a mask. Messages
are added to this queue separate of other CAN APIs. This means the
messages can be receive without impacting other APIs such as
vendor APIs.

This enables things like detection of what devices are on the
bus, or custom decoding, without using vendor APIs.

Co-authored-by: Thad House <thadhouse1@gmail.com>
2022-12-06 21:58:09 -08:00
Peter Johnson
be27171236 [wpilibj] Shuffleboard: Check for null sendable (#4772)
Adding a null sendable to a container could result in a delayed NullPointerException.
2022-12-06 21:14:09 -08:00
Starlight220
4bbdbdfb48 [commands] Move GroupedCommands to CommandScheduler (#4728)
Move the command group checking functionality from CommandGroupBase into CommandScheduler.
Update references to grouping as composition for clarity (because explicitly grouping isn't the only way to do it).
Deprecate the static factory methods parallel, race, and deadline in CommandGroupBase in favor of the identical ones in Commands.
2022-12-06 21:13:31 -08:00
Tyler Veness
f18fd41ac3 [wpimath] Remove broken and obsoleted ComputerVisionUtil functions (#4775)
The ComputerVisionUtil class was added before AprilTag support was
announced. Now that it has, the functions for estimating a pose from
range and yaw are no longer needed; it's just better to get the pose
directly from the AprilTag.

The coordinate system on some function arguments was confusing or didn't
match the NWU convention the rest of the library uses. It's easier to
remove the functions now and add them back after they're fixed since the
fixes aren't trivial.

The range function was removed because it uses pitch and yaw in the
camera's spherical coordinate system, which is obsoleted by AprilTags.
AprilTags give you a 6DOF pose directly, so range can be obtained via
Pose2d.getTranslation().getDistance().

Fixes #4757.
2022-12-06 21:11:27 -08:00
Peter Johnson
2d0faecf4f [glass] DataSource: Add spinlock to protect value (#4771)
A lock is needed here due to HAL async callbacks.
2022-12-06 21:09:14 -08:00
Peter Johnson
348bd107fc [hal] Add CANManufacturer for The Thrifty Bot (#4773) 2022-12-06 21:08:45 -08:00
Starlight220
3149dc64b8 [examples] HatchbotInlined: Use Subsystem factories (#4765) 2022-12-06 15:10:39 -08:00
Jordan McMichael
8618dd4160 [glass, wpilib] Replace remaining references to Speed Controller with Motor Controller (#4769) 2022-12-05 20:06:43 -08:00
Peter Johnson
72e21a1ed1 [apriltag] Use wpilibsuite fork of apriltag (#4764) 2022-12-05 19:59:24 -08:00
Ryan Blue
eab0d929e6 [commands] CommandGenericHID POV methods: Fix docs (NFC) (#4760) 2022-12-05 13:46:46 -08:00
Ryan Blue
6789869663 [wpilib] Call set(0) rather than disable for stopMotor (#4763)
For PWM motor controllers, this is going to be the fastest way to stop the motor,
as disabling the PWM output will have a small delay due to the motor controller
needing to detect the missing signal.

Note Set(0) is not a safe approach for CAN motor controllers, which may have closed
loop operation, non-% output set() calls, etc.
2022-12-05 13:33:54 -08:00
Dustin Spicuzza
c9dea2968d [cscore] Emit warning that USB Camera isn't supported on OSX (#4766)
Reduces user confusion, as right now there's really no indication that something is wrong other than there's no camera,
2022-12-05 13:30:13 -08:00
sciencewhiz
8f402645f5 [commands] Fix PIDSubsystem setSetpoint behavior (#4759)
No longer stores a temporary setpoint in PIDSubsystem, instead
immediately sending to PIDController. This fixes an issue where the
setpoint didn't take effect until the Subsystem Periodic method ran, and
could cause commands to finish early if they were scheduled after the
subsystem periodic method ran because it used the old setpoint.
2022-12-03 11:32:46 -08:00
Tyler Veness
f24ad1d715 [build] Upgrade to googletest 1.12.1 (#4752)
This fixes GCC 12 warnings for googletest internals.
2022-12-03 11:32:08 -08:00
Jaci Brunning
ff88756864 [wpimath] Add new DCMotor functions for alternative calculations and reduction calculation (#4749) 2022-12-03 09:47:16 -08:00
superpenguin612
f58873db8e [wpimath] Remove extra terms in matrix for pose estimator docs (#4756) 2022-12-03 09:46:34 -08:00
superpenguin612
37e969b41a [wpimath] Add constructors to pose estimators with default standard deviations (#4754) 2022-12-03 09:46:10 -08:00
Tyler Veness
13cdc29382 [ci] Rename comment command from "/wpiformat" to "/format" (#4755)
It now runs more than just wpiformat.
2022-12-02 18:24:06 -08:00
Tyler Veness
6e23985ae6 [examples] Add main include directory to test builds (#4751)
This fixes the following compilation errors:
```
/home/tav/frc/wpilib/allwpilib/wpilibcExamples/src/main/cpp/examples/UnitTest/cpp/subsystems/Intake.cpp:5:10: fatal error: subsystems/Intake.h: No such file or directory
    5 | #include "subsystems/Intake.h"
      |          ^~~~~~~~~~~~~~~~~~~~~

/home/tav/frc/wpilib/allwpilib/wpilibcExamples/src/test/cpp/examples/UnitTest/cpp/subsystems/IntakeTest.cpp:11:10: fatal error: Constants.h: No such file or directory
   11 | #include "Constants.h"
      |          ^~~~~~~~~~~~~
```
2022-12-02 10:37:49 -08:00
Starlight220
66bb0ffb2c [examples] Add unit testing infrastructure (#4646) 2022-12-02 08:40:14 -08:00
Tyler Veness
74cc86c4c5 [wpimath] Make transform tests use pose/transform equality operators (#4675)
Also add more nonzeros to Transform3d tests to make them more
comprehensive.
2022-12-02 08:36:57 -08:00
Jordan McMichael
e22d8cc343 [wpimath] Use Odometry for internal state in Pose Estimation (#4668)
This effectively replaces the Unscented Kalman Filter used for Pose Estimation with the Odometry model, and uses a recalculable Kalman gain matrix to update pose estimations whenever a vision measurement is added.

Notably, this change removes the need for the confusing generics used in Java, and the C++ implementation got quite a bit less complex as well.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2022-12-02 08:36:10 -08:00
Ryan Blue
68dba92630 [ci] Update mac and windows builds to Java 17 (#4750) 2022-12-02 08:31:38 -08:00
Tyler Veness
23bfc2d9ab [sim] Remove unmaintained Gazebo support (#4736) 2022-12-01 20:46:47 -08:00
Starlight220
1f1461e254 [wpilib] Add method to enable/disable LiveWindow in test mode (#4678) 2022-12-01 13:28:06 -08:00
Tyler Veness
eae68fc165 [wpimath] Add tolerance for Rotation3d rotation matrix special orthogonality (#4744) 2022-11-30 19:51:31 -08:00
Tyler Veness
4c4545fb4b [apriltag] Suppress warning (#4743) 2022-11-30 19:51:18 -08:00
Tyler Veness
16ffaa754d [docs] Generate docs for apriltag subproject (#4745) 2022-11-30 19:50:52 -08:00
Thad House
5e74ff26d8 [apriltag, build] Update native utils, add apriltag impl and JNI (#4733)
Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
2022-11-30 00:16:29 -08:00
Dustin Spicuzza
53875419a1 [hal] Allow overriding stderr printing by HAL_SendError (#4742) 2022-11-29 23:19:15 -08:00
Peter Johnson
aa6499e920 [ntcore] Fix special topic multi-subscriber handling (#4740)
It now matches server behavior, where "" doesn't match special topics.

Also fixes duplicate notification that could occur in some cases.
2022-11-29 21:58:57 -08:00
Peter Johnson
df70351107 [build] Fix cmake install of thirdparty includes (#4741) 2022-11-29 21:58:38 -08:00
Peter Johnson
e9bd50ff9b [glass] NT view: clear meta-topic info on disconnect (#4732) 2022-11-29 21:57:48 -08:00
Peter Johnson
9b319fd56b [ntcore] Add sub option for local vs remote changes (#4731)
This is the subscriber readQueue version of the local value listener flag.
2022-11-29 21:57:20 -08:00
Peter Johnson
18d28ec5e3 [ntcore] Remove duplicate value checking from ClientImpl
This is no longer required with the duplicate value checking in
LocalStorage.
2022-11-29 09:49:47 -08:00
Peter Johnson
bdfb625211 [ntcore] Send duplicate values to network if necessary
Essentially this separates duplicate value detection between network and
local.
2022-11-29 09:49:47 -08:00
Ryan Blue
21003e34eb [commands] Update Subsystem factories and example to return CommandBase (#4729)
Also update rapidreactcommandbot example factories to fit this convention (as in #4655).

C++ does not need an update as CommandPtr already uses CommandBase (#4677).
2022-11-28 19:47:18 -08:00
Starlight220
70080457d5 [commands] Refactor ProxyScheduleCommand, SelectCommand into ProxyCommand (#4534) 2022-11-28 14:43:10 -08:00
Colin Wong
e82cd5147b [wpilib] Tweak Color HSV formula and use in AddressableLED (#4724)
The Color algorithm was tweaked to:
a) not produce incorrect values if the user happens to input a hue outside the [0, 180) range, and
b) more accurately convert the hue remainder from range 0-30 to 0-255. The current conversion vastly overshoots the multiplier (converts 0-30 to 0-270) and relies on clamping the value when constructing the Color object to produce a slightly incorrect result.
2022-11-28 14:42:22 -08:00
Colin Wong
ec124bb662 [commands] Allow unsetting a subsystem's default command (#4621) 2022-11-28 14:03:14 -08:00
Ryan Blue
2b2aa8eef7 [examples] Update all examples to use NWU coordinate conventions (#4725) 2022-11-28 13:49:49 -08:00
Starlight220
cb38bacfe8 [commands] Revert to original Trigger implementation (#4673)
Trigger was refactored to use BooleanEvent when it was introduced in #4104.
This reverts to the original implementation until edge-based BooleanEvents can be fixed.
2022-11-28 13:48:48 -08:00
Starlight220
15561338d5 [commands] Remove one more default command isFinished check (#4727) 2022-11-28 13:44:37 -08:00
Ryan Blue
ca35a2e097 Add simgui files to .gitignore (#4726) 2022-11-28 08:57:57 -08:00
Starlight220
20dbae0cee [examples] Renovate command-based examples (#4409)
Refactor some examples to use newer features, such as HID factories, library-provided command factories, CommandPtr (C++), as well as new idioms such as static/instance command factories.
2022-11-28 08:55:13 -08:00
Eli Barnett
1a59737f40 [commands] Add convenience factories (#4460)
Co-authored-by: Starlight220 <53231611+Starlight220@users.noreply.github.com>
2022-11-28 07:41:25 -08:00
Tyler Veness
42b6d4e3f7 Use defaulted comparison operators in C++ (#4723)
Comparison operators which compared against every class member variable
now use C++20's default comparison operators.

Also remove operator!= that in C++20 is now auto-generated from operator==.
2022-11-27 21:01:01 -08:00
Peter Johnson
135c13958f [wpigui] Add FontAwesome (#4713) 2022-11-27 20:00:17 -08:00
Peter Johnson
ffbfc61532 [ntcore] Add NetworkTable table-specific listeners (#4640)
These are similar, but not quite identical to, the NT3 NetworkTable
table listeners.

Also add table topic-only multi-subscriber to ensure functions like
getKeys() work properly regardless of other subscriptions.
2022-11-27 19:46:34 -08:00
Starlight220
8958b2a4da [commands] Add property tests for command compositions (#4715) 2022-11-27 16:23:56 -08:00
Starlight220
e4ac09077c [wpilib] Add link to MotorSafety article (#4720) 2022-11-27 16:23:06 -08:00
Starlight220
f40de0c120 [commands] Add C++ factory templates (#4686) 2022-11-27 11:27:44 -08:00
Peter Johnson
51fa3e851f [build] cmake: Use FetchContent instead of ExternalProject (#4714)
Also switch to using thirdparty-fonts instead of generating them.
2022-11-26 23:05:41 -08:00
Peter Johnson
1da84b2255 [wpigui] Reload fonts to scale rather than preloading (#4712) 2022-11-26 22:30:38 -08:00
Peter Johnson
e43e2fbc84 [wpiutil] StringExtras: Add UnescapeCString (#4707)
Based on implementation in glass but enhanced for generic use.
2022-11-26 18:21:45 -08:00
Peter Johnson
5804d8fa84 [ntcore] Server: Properly handle multiple subscribers (#4717)
Previously, only the first subscriber was actually matched to a topic
when a topic was created; this was a problem when later publishing
values as a client could have both a topic-only subscriber and a normal
subscriber, and only the first one would end up being subscribed to the
topic.
2022-11-26 17:02:22 -08:00
Peter Johnson
169ef5fabf [glass] Update NT view for topicsOnly and sendAll changes (#4718) 2022-11-26 17:01:40 -08:00
Starlight220
148759ef54 [examples] CANPDP: Expand properties shown (#4687) 2022-11-25 23:51:15 -08:00
Starlight220
58ed112b51 [commands] RepeatCommand: restart on following iteration (#4706)
This fixes InstantCommand.repeatedly().
2022-11-25 23:50:42 -08:00
Ryan Blue
dd1da77d20 [readme] Fix broken CI badge (#4710) 2022-11-25 23:49:47 -08:00
Ryan Blue
7cda85df20 [build] Check Gradle plugin repo last to fix CI (#4711) 2022-11-25 23:48:18 -08:00
Thad House
7ed9b13277 [build] Bump version plugin to fix null tag (#4705) 2022-11-24 22:10:59 -08:00
Tyler Veness
6b4f26225d [apriltag] Fix pluralization of apriltag artifacts (#4671) 2022-11-24 09:06:38 -08:00
Peter Johnson
b2d2924b72 [cscore] Add Y16 image support (#4702) 2022-11-24 09:06:06 -08:00
Peter Johnson
34ec89c041 [wpilibc] Shuffleboard SimpleWidget: Return pointer instead of reference (#4703)
Based on beta test feedback, returning a pointer is more intuitive, as
typically the return value is late bound to an instance variable.
2022-11-24 09:05:37 -08:00
Peter Johnson
e15200068d [ci] Disable HW testbench runs (#4704)
These are currently broken with no timetable to fix.
2022-11-24 09:04:57 -08:00
Starlight220
d5200db6cd [wpimath] Rename HolonomicDriveController.calculate params (#4683) 2022-11-23 23:13:50 -08:00
Tyler Veness
2ee3d86de4 [wpimath] Clarify Rotation3d roll-pitch-yaw direction (#4699) 2022-11-23 23:12:59 -08:00
Peter Johnson
9f0a8b930f [cscore] Use MFVideoFormat_L8 for Gray on Windows (#4701) 2022-11-23 22:15:56 -08:00
Peter Johnson
2bca43779e [cscore] Add UYVY image support (#4700) 2022-11-23 22:00:31 -08:00
ohowe
4307d0ee8b [glass] Plot: allow for more than 11 plots (#4685)
Since m_windows is sorted using the ascii, when "Plot <10>" is reached it will be before "Plot <2>" in `m_windows` which makes it so it will not add a new plot after the id 10 is reached. This also fixes a potential issue of someone manually changing an id in the file, which would break adding a new plot in some circumstances.
2022-11-23 13:57:29 -08:00
Starlight220
3fe8d355a1 [examples] StateSpaceDifferentialDriveSimulation: Use encoder reversed constants (#4682)
This matches the Java example.
2022-11-22 10:29:40 -08:00
Peter Johnson
b44034dadc [ntcore] Allow duplicate client IDs on server (#4676)
Currently, the server rejects duplicate client IDs. As we want to make
the client implementation as simple as possible, instead deduplicate the
name on the server side by appending "@" and a count.

NT4 spec has been updated for this change.
2022-11-22 10:27:49 -08:00
Starlight220
52d2c53888 [commands] Rename Java factory wait() to waitSeconds() (#4684)
This is needed to avoid a conflict with Object.wait() when using static imports.
C++ doesn't have this issue, and has units, so Wait() still makes sense there.
2022-11-22 10:16:27 -08:00
Thad House
76e918f71e [build] Fix JNI artifacts linking to incorrect libraries (#4680) 2022-11-21 20:33:46 -08:00
Starlight220
0bee875aff [commands] Change C++ CommandPtr to use CommandBase (#4677) 2022-11-21 09:45:50 -08:00
Peter Johnson
98e922313b [glass] Don't check IsConnected for NT widgets (#4674)
Checking this isn't required, and prevented these widgets from working
in the simulation GUI without another NT client connected.
2022-11-20 17:27:28 -08:00
amquake
9a36373b8f [apriltag] Switch 2022 apriltag layout length and width values (#4670) 2022-11-19 09:11:08 -08:00
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
Thad House
8153911160 [build] Fix MSVC runtime archiver to grab default runtime (#4478) 2022-10-16 15:37:13 -07:00
Tyler Veness
fbdc810887 Upgrade to C++20 (#4239)
* Use explicit this capture required by C++20
* Use C++20 span
* Replace wpi::numbers with std::numbers
* Fix C++20 clang-tidy warning false positive in fmt
* Remove ciso646 include since C++20 removed that header
* Fix global-buffer-overflow asan warnings in ntcore tests
* Add DIOSetProxy constructor to HAL

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

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

* Formatting

* Fix tidy

* Formatting

* Fix loading

* Make interrupt api public

* Add multiple wait api

* Formatting

* Fix build

* Fix review comments

* wpiformat

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

View File

@@ -12,9 +12,9 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: ubuntu-22.04
name: Linux
container: wpilib/roborio-cross-ubuntu:2022-20.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
flags: ""
- os: macOS-11
name: macOS
@@ -27,12 +27,17 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3
- name: Install opencv (macOS)
run: brew install opencv
if: runner.os == 'macOS'
- name: Set up Python 3.8
uses: actions/setup-python@v2
- name: Set up Python 3.8 (macOS)
if: runner.os == 'macOS'
uses: actions/setup-python@v4
with:
python-version: 3.8

View File

@@ -4,9 +4,9 @@ on:
types: [ created ]
jobs:
wpiformat:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/wpiformat')
runs-on: ubuntu-latest
format:
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/format')
runs-on: ubuntu-22.04
steps:
- name: React Rocket
uses: actions/github-script@v4
@@ -37,21 +37,28 @@ 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
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Commit
git commit -am "wpiformat"
git commit -am "Formatting fixes"
git push

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

@@ -1,19 +0,0 @@
name: Gazebo
on: [pull_request, push]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
container: wpilib/gazebo-ubuntu:20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Build with Gradle
run: ./gradlew simulation:frc_gazebo_plugins:build simulation:halsim_gazebo:build -PbuildServer -PforceGazebo

View File

@@ -12,20 +12,20 @@ jobs:
fail-fast: false
matrix:
include:
- container: wpilib/roborio-cross-ubuntu:2022-20.04
- container: wpilib/roborio-cross-ubuntu:2023-22.04
artifact-name: Athena
build-options: "-Ponlylinuxathena"
- container: wpilib/raspbian-cross-ubuntu:10-20.04
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
artifact-name: Arm32
build-options: "-Ponlylinuxarm32"
- container: wpilib/aarch64-cross-ubuntu:bionic-20.04
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
artifact-name: Arm64
build-options: "-Ponlylinuxarm64"
- container: wpilib/ubuntu-base:20.04
- container: wpilib/ubuntu-base:22.04
artifact-name: Linux
build-options: "-Ponlylinuxx86-64"
name: "Build - ${{ matrix.artifact-name }}"
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
@@ -51,30 +51,38 @@ jobs:
fail-fast: false
matrix:
include:
- os: windows-2019
- os: windows-2022
artifact-name: Win64Debug
architecture: x64
task: "build"
build-options: "-PciDebugOnly --max-workers 1"
- os: windows-2019
outputs: "build/allOutputs"
- os: windows-2022
artifact-name: Win64Release
architecture: x64
build-options: "-PciReleaseOnly --max-workers 1"
task: "copyAllOutputs"
outputs: "build/allOutputs"
- os: macOS-11
artifact-name: macOS
architecture: x64
build-options: "-Pbuildalldesktop"
task: "build"
outputs: "build/allOutputs"
- os: windows-2022
artifact-name: Win32
architecture: x86
task: ":ntcoreffi:build"
outputs: "ntcoreffi/build/outputs"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
java-version: 11
distribution: 'zulu'
java-version: 17
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
uses: wpilibsuite/import-signing-certificate@v1
@@ -94,33 +102,35 @@ jobs:
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
shell: bash
if: startsWith(github.ref, 'refs/tags/v')
- name: Set Java Heap Size
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
if: matrix.artifact-name == 'Win32'
- name: Build with Gradle
run: ./gradlew ${{ matrix.task }} --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
env:
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: build/allOutputs
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')
@@ -129,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
@@ -137,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
@@ -151,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: |
@@ -177,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,15 +41,23 @@ jobs:
- name: Generate diff
run: git diff HEAD > wpiformat-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: wpiformat fixes
path: wpiformat-fixes.patch
if: ${{ failure() }}
- name: Write to job summary
run: |
echo '```diff' >> $GITHUB_STEP_SUMMARY
cat wpiformat-fixes.patch >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
if: ${{ failure() }}
tidy:
name: "clang-tidy"
runs-on: ubuntu-latest
container: wpilib/roborio-cross-ubuntu:2022-20.04
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -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,8 +86,8 @@ 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
container: wpilib/ubuntu-base:20.04
runs-on: ubuntu-22.04
container: wpilib/ubuntu-base:22.04
steps:
- uses: actions/checkout@v3
- name: Fetch all history and metadata
@@ -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

@@ -15,40 +15,29 @@ jobs:
- name: asan
cmake-flags: "-DCMAKE_BUILD_TYPE=Asan"
ctest-env: ""
ctest-flags: "-E 'ntcore|wpilibc'"
ctest-flags: "-E 'wpilibc'"
- name: tsan
cmake-flags: "-DCMAKE_BUILD_TYPE=Tsan"
ctest-env: "TSAN_OPTIONS=second_deadlock_stack=1"
ctest-flags: "-E 'ntcore|cscore|cameraserver|wpilibc|wpilibNewCommands'"
ctest-flags: "-E 'cscore|cameraserver|wpilibc|wpilibNewCommands'"
- name: ubsan
cmake-flags: "-DCMAKE_BUILD_TYPE=Ubsan"
ctest-env: ""
ctest-flags: ""
name: "${{ matrix.name }}"
runs-on: ubuntu-latest
container: wpilib/roborio-cross-ubuntu:2022-20.04
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt install -y gcc-11 g++-11
sudo update-alternatives \
--install /usr/bin/gcc gcc /usr/bin/gcc-11 11 \
--slave /usr/bin/g++ g++ /usr/bin/g++-11
sudo update-alternatives --set gcc /usr/bin/gcc-11
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14
- name: Install jinja
run: python -m pip install jinja2
- name: configure
run: mkdir build && cd build && cmake ${{ matrix.cmake-flags }} ..
run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang-14 -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++-14 ${{ matrix.cmake-flags }} ..
- name: build
working-directory: build

View File

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

4
.gitignore vendored
View File

@@ -5,6 +5,10 @@ doxygen.log
build*/
!buildSrc/
simgui-ds.json
simgui-window.json
simgui.json
# Created by the jenkins test script
test-reports

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/)
[![Gradle](https://github.com/wpilibsuite/allwpilib/actions/workflows/gradle.yml/badge.svg?branch=main)](https://github.com/wpilibsuite/allwpilib/actions/workflows/gradle.yml)
[![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.
@@ -15,7 +15,6 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
- [Faster builds](#faster-builds)
- [Using Development Builds](#using-development-builds)
- [Custom toolchain location](#custom-toolchain-location)
- [Gazebo simulation](#gazebo-simulation)
- [Formatting/Linting](#formattinglinting)
- [CMake](#cmake)
- [Publishing](#publishing)
@@ -33,7 +32,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
@@ -47,8 +46,8 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
- On Windows, install the JDK 11 .msi from the link above
- On macOS, install the JDK 11 .pkg from the link above
- C++ compiler
- On Linux, install GCC 8 or greater
- On Windows, install [Visual Studio Community 2022 or 2019](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
- On Linux, install GCC 11 or greater
- On Windows, install [Visual Studio Community 2022](https://visualstudio.microsoft.com/vs/community/) and select the C++ programming language during installation (Gradle can't use the build tools for Visual Studio)
- On macOS, install the Xcode command-line build tools via `xcode-select --install`
- ARM compiler toolchain
- Run `./gradlew installRoboRioToolchain` after cloning this repository
@@ -62,7 +61,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 +103,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
@@ -114,30 +113,13 @@ If you have installed the FRC Toolchain to a directory other than the default, o
./gradlew build -PtoolChainPath=some/path/to/frc/toolchain/bin
```
### Gazebo simulation
If you also want to force building Gazebo simulation support, add -PforceGazebo. This requires gazebo_transport. We have tested on 14.04 and 15.05, but any correct install of Gazebo should work, even on Windows if you build Gazebo from source. Correct means CMake needs to be able to find gazebo-config.cmake. See [The Gazebo website](https://gazebosim.org/) for installation instructions.
```bash
./gradlew build -PforceGazebo
```
If you prefer to use CMake directly, the you can still do so.
The common CMake tasks are wpilibcSim, frc_gazebo_plugins, and gz_msgs
```bash
mkdir build #run this in the root of allwpilib
cd build
cmake ..
make
```
### Formatting/linting
Once a PR has been submitted, formatting can be run in CI by commenting `/format` on the PR. A new commit will be pushed with the formatting changes.
#### 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.
#### Java Code Quality Tools
@@ -162,9 +144,7 @@ The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
## Structure and Organization
The main WPILib code you're probably looking for is in WPILibJ and WPILibC. Those directories are split into shared, sim, and athena. Athena contains the WPILib code meant to run on your roboRIO. Sim is WPILib code meant to run on your computer with Gazebo, and shared is code shared between the two. Shared code must be platform-independent, since it will be compiled with both the ARM cross-compiler and whatever desktop compiler you are using (g++, msvc, etc...).
The Simulation directory contains extra simulation tools and libraries, such as gz_msgs and JavaGazebo. See sub-directories for more information.
The main WPILib code you're probably looking for is in WPILibJ and WPILibC. Those directories are split into shared, sim, and athena. Athena contains the WPILib code meant to run on your roboRIO. Sim is WPILib code meant to run on your computer, and shared is code shared between the two. Shared code must be platform-independent, since it will be compiled with both the ARM cross-compiler and whatever desktop compiler you are using (g++, msvc, etc...).
The integration test directories for C++ and Java contain test code that runs on our test-system. When you submit code for review, it is tested by those programs. If you add new functionality you should make sure to write tests for it so we don't break it in the future.

View File

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

118
apriltag/CMakeLists.txt Normal file
View File

@@ -0,0 +1,118 @@
project(apriltag)
include(CompileWarnings)
include(GenResources)
include(FetchContent)
FetchContent_Declare(
apriltaglib
GIT_REPOSITORY https://github.com/wpilibsuite/apriltag.git
GIT_TAG ad31e33d20f9782b7239cb15cde57c56c91383ad
)
# Don't use apriltag's CMakeLists.txt due to conflicting naming and JNI
FetchContent_GetProperties(apriltaglib)
if(NOT apriltaglib_POPULATED)
FetchContent_Populate(apriltaglib)
endif()
aux_source_directory(${apriltaglib_SOURCE_DIR}/common APRILTAGLIB_COMMON_SRC)
file(GLOB TAG_FILES ${apriltaglib_SOURCE_DIR}/tag*.c)
set(APRILTAGLIB_SRCS ${apriltaglib_SOURCE_DIR}/apriltag.c ${apriltaglib_SOURCE_DIR}/apriltag_pose.c ${apriltaglib_SOURCE_DIR}/apriltag_quad_thresh.c)
file(GLOB apriltag_jni_src src/main/native/cpp/jni/AprilTagJNI.cpp)
if (WITH_JAVA)
find_package(Java REQUIRED)
find_package(JNI REQUIRED)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
set(CMAKE_JNI_TARGET true)
file(GLOB EJML_JARS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/*.jar")
file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar")
find_file(OPENCV_JAR_FILE
NAMES opencv-${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.jar
PATHS ${OPENCV_JAVA_INSTALL_DIR} ${OpenCV_INSTALL_PATH}/bin ${OpenCV_INSTALL_PATH}/share/java
NO_DEFAULT_PATH)
set(CMAKE_JAVA_INCLUDE_PATH apriltag.jar ${EJML_JARS} ${JACKSON_JARS})
file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
file(GLOB_RECURSE JAVA_RESOURCES src/main/native/resources/*.json)
add_jar(apriltag_jar
SOURCES ${JAVA_SOURCES}
RESOURCES NAMESPACE "edu/wpi/first/apriltag" ${JAVA_RESOURCES}
INCLUDE_JARS wpimath_jar ${EJML_JARS} wpiutil_jar ${OPENCV_JAR_FILE}
OUTPUT_NAME apriltag
GENERATE_NATIVE_HEADERS apriltag_jni_headers)
get_property(APRILTAG_JAR_FILE TARGET apriltag_jar PROPERTY JAR_FILE)
install(FILES ${APRILTAG_JAR_FILE} DESTINATION "${java_lib_dest}")
set_property(TARGET apriltag_jar PROPERTY FOLDER "java")
add_library(apriltagjni ${apriltag_jni_src})
wpilib_target_warnings(apriltagjni)
target_link_libraries(apriltagjni PUBLIC apriltag)
set_property(TARGET apriltagjni PROPERTY FOLDER "libraries")
target_link_libraries(apriltagjni PRIVATE apriltag_jni_headers)
add_dependencies(apriltagjni apriltag_jar)
if (MSVC)
install(TARGETS apriltagjni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
install(TARGETS apriltagjni EXPORT apriltagjni DESTINATION "${main_lib_dest}")
endif()
GENERATE_RESOURCES(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltag_resources_src)
file(GLOB apriltag_native_src src/main/native/cpp/*.cpp)
add_library(apriltag ${apriltag_native_src} ${apriltag_resources_src} ${APRILTAGLIB_SRCS} ${APRILTAGLIB_COMMON_SRC} ${TAG_FILES})
set_target_properties(apriltag PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET apriltag PROPERTY FOLDER "libraries")
target_compile_features(apriltag PUBLIC cxx_std_20)
wpilib_target_warnings(apriltag)
# disable warnings that apriltaglib can't handle
if (MSVC)
target_compile_options(apriltag PRIVATE /wd4018)
else()
target_compile_options(apriltag PRIVATE -Wno-sign-compare -Wno-gnu-zero-variadic-macro-arguments)
endif()
target_link_libraries(apriltag wpimath)
target_include_directories(apriltag PUBLIC
$<BUILD_INTERFACE:${apriltaglib_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/apriltag>)
install(TARGETS apriltag EXPORT apriltag DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/apriltag")
if (WITH_JAVA AND MSVC)
install(TARGETS apriltag RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
if (WITH_FLAT_INSTALL)
set (apriltag_config_dir ${wpilib_dest})
else()
set (apriltag_config_dir share/apriltag)
endif()
configure_file(apriltag-config.cmake.in ${WPILIB_BINARY_DIR}/apriltag-config.cmake )
install(FILES ${WPILIB_BINARY_DIR}/apriltag-config.cmake DESTINATION ${apriltag_config_dir})
install(EXPORT apriltag DESTINATION ${apriltag_config_dir})
if (WITH_TESTS)
wpilib_add_test(apriltag src/test/native/cpp)
target_include_directories(apriltag_test PRIVATE src/test/native/include)
target_link_libraries(apriltag_test apriltag gmock_main)
endif()

View File

@@ -0,0 +1,7 @@
include(CMakeFindDependencyMacro)
@FILENAME_DEP_REPLACE@
@WPIMATH_DEP_REPLACE@
@WPIUTIL_DEP_REPLACE@
@FILENAME_DEP_REPLACE@
include(${SELF_DIR}/apriltag.cmake)

84
apriltag/build.gradle Normal file
View File

@@ -0,0 +1,84 @@
apply from: "${rootDir}/shared/resources.gradle"
ext {
nativeName = 'apriltag'
devMain = 'edu.wpi.first.apriltag.DevMain'
useJava = true
def generateTask = createGenerateResourcesTask('main', 'APRILTAG', 'frc', project)
tasks.withType(CppCompile) {
dependsOn generateTask
}
splitSetup = {
it.sources {
resourcesCpp(CppSourceSet) {
source {
srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib"
include '*.cpp'
}
}
}
}
}
evaluationDependsOn(':wpimath')
apply from: "${rootDir}/shared/jni/setupBuild.gradle"
apply from: "${rootDir}/shared/apriltaglib.gradle"
apply from: "${rootDir}/shared/opencv.gradle"
dependencies {
implementation project(':wpimath')
devImplementation 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'
if (it.component.name == "${nativeName}JNI") {
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
} else {
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
}
nativeUtils.useRequiredLibrary(it, 'apriltaglib')
}
}
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,18 @@
// 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 edu.wpi.first.apriltag.jni.AprilTagJNI;
public final class DevMain {
/** Main entry point. */
public static void main(String[] args) {
System.out.println("Hello World!");
var detector = AprilTagJNI.aprilTagCreate("tag16h5", 2.0, 0.0, 1, false, false);
AprilTagJNI.aprilTagDestroy(detector);
}
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,235 @@
// 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 in the JSON are measured using the normal FRC coordinate system, NWU with the origin
* at the bottom-right corner of the blue alliance wall. {@link #setOrigin(OriginPosition)} can be
* used to change the poses returned from {@link AprilTagFieldLayout#getTagPose(int)} to be from the
* perspective of a specific alliance.
*/
@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,89 @@
// 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.jni;
import edu.wpi.first.util.RuntimeLoader;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Mat;
public class AprilTagJNI {
static boolean libraryLoaded = false;
static RuntimeLoader<AprilTagJNI> loader = null;
public static class Helper {
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
public static boolean getExtractOnStaticLoad() {
return extractOnStaticLoad.get();
}
public static void setExtractOnStaticLoad(boolean load) {
extractOnStaticLoad.set(load);
}
}
static {
if (Helper.getExtractOnStaticLoad()) {
try {
loader =
new RuntimeLoader<>(
"apriltagjni", RuntimeLoader.getDefaultExtractionRoot(), AprilTagJNI.class);
loader.loadLibrary();
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
libraryLoaded = true;
}
}
// Returns a pointer to a apriltag_detector_t
public static native long aprilTagCreate(
String fam, double decimate, double blur, int threads, boolean debug, boolean refine_edges);
// Destroy and free a previously created detector.
public static native void aprilTagDestroy(long detector);
private static native Object[] aprilTagDetectInternal(
long detector,
long imgAddr,
int rows,
int cols,
boolean doPoseEstimation,
double tagWidth,
double fx,
double fy,
double cx,
double cy,
int nIters);
// Detect targets given a GRAY frame. Returns a pointer toa zarray
public static DetectionResult[] aprilTagDetect(
long detector,
Mat img,
boolean doPoseEstimation,
double tagWidth,
double fx,
double fy,
double cx,
double cy,
int nIters) {
return (DetectionResult[])
aprilTagDetectInternal(
detector,
img.dataAddr(),
img.rows(),
img.cols(),
doPoseEstimation,
tagWidth,
fx,
fy,
cx,
cy,
nIters);
}
}

View File

@@ -0,0 +1,226 @@
// 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.jni;
import edu.wpi.first.math.MatBuilder;
import edu.wpi.first.math.Matrix;
import edu.wpi.first.math.Nat;
import edu.wpi.first.math.geometry.Rotation3d;
import edu.wpi.first.math.geometry.Transform3d;
import edu.wpi.first.math.geometry.Translation3d;
import edu.wpi.first.math.numbers.N3;
import java.util.Arrays;
import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
import org.ejml.simple.SimpleMatrix;
public class DetectionResult {
public int getId() {
return m_id;
}
public int getHamming() {
return m_hamming;
}
public float getDecisionMargin() {
return m_decisionMargin;
}
public void setDecisionMargin(float decisionMargin) {
this.m_decisionMargin = decisionMargin;
}
@SuppressWarnings("PMD.MethodReturnsInternalArray")
public double[] getHomography() {
return m_homography;
}
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public void setHomography(double[] homography) {
this.m_homography = homography;
}
public double getCenterX() {
return m_centerX;
}
public void setCenterX(double centerX) {
this.m_centerX = centerX;
}
public double getCenterY() {
return m_centerY;
}
public void setCenterY(double centerY) {
this.m_centerY = centerY;
}
@SuppressWarnings("PMD.MethodReturnsInternalArray")
public double[] getCorners() {
return m_corners;
}
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public void setCorners(double[] corners) {
this.m_corners = corners;
}
public double getError1() {
return m_error1;
}
public double getError2() {
return m_error2;
}
public Transform3d getPoseResult1() {
return m_poseResult1;
}
public Transform3d getPoseResult2() {
return m_poseResult2;
}
private final int m_id;
private final int m_hamming;
private float m_decisionMargin;
private double[] m_homography;
private double m_centerX;
private double m_centerY;
private double[] m_corners;
private final Transform3d m_poseResult1;
private final double m_error1;
private final Transform3d m_poseResult2;
private final double m_error2;
/**
* Constructs a new detection result. Used from JNI.
*
* @param id id
* @param hamming hamming
* @param decisionMargin dm
* @param homography homography
* @param centerX centerX
* @param centerY centerY
* @param corners corners
* @param pose1TransArr pose1TransArr
* @param pose1RotArr pose1RotArr
* @param err1 err1
* @param pose2TransArr pose2TransArr
* @param pose2RotArr pose2RotArr
* @param err2 err2
*/
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public DetectionResult(
int id,
int hamming,
float decisionMargin,
double[] homography,
double centerX,
double centerY,
double[] corners,
double[] pose1TransArr,
double[] pose1RotArr,
double err1,
double[] pose2TransArr,
double[] pose2RotArr,
double err2) {
this.m_id = id;
this.m_hamming = hamming;
this.m_decisionMargin = decisionMargin;
this.m_homography = homography;
this.m_centerX = centerX;
this.m_centerY = centerY;
this.m_corners = corners;
this.m_error1 = err1;
this.m_poseResult1 =
new Transform3d(
new Translation3d(pose1TransArr[0], pose1TransArr[1], pose1TransArr[2]),
new Rotation3d(
orthogonalizeRotationMatrix(
new MatBuilder<>(Nat.N3(), Nat.N3()).fill(pose1RotArr))));
this.m_error2 = err2;
this.m_poseResult2 =
new Transform3d(
new Translation3d(pose2TransArr[0], pose2TransArr[1], pose2TransArr[2]),
new Rotation3d(
orthogonalizeRotationMatrix(
new MatBuilder<>(Nat.N3(), Nat.N3()).fill(pose2RotArr))));
}
/**
* Get the ratio of pose reprojection errors, called ambiguity. Numbers above 0.2 are likely to be
* ambiguous.
*
* @return The ratio of pose reprojection errors.
*/
public double getPoseAmbiguity() {
var min = Math.min(m_error1, m_error2);
var max = Math.max(m_error1, m_error2);
if (max > 0) {
return min / max;
} else {
return -1;
}
}
@Override
public String toString() {
return "DetectionResult [centerX="
+ m_centerX
+ ", centerY="
+ m_centerY
+ ", corners="
+ Arrays.toString(m_corners)
+ ", decisionMargin="
+ m_decisionMargin
+ ", error1="
+ m_error1
+ ", error2="
+ m_error2
+ ", hamming="
+ m_hamming
+ ", homography="
+ Arrays.toString(m_homography)
+ ", id="
+ m_id
+ ", poseResult1="
+ m_poseResult1
+ ", poseResult2="
+ m_poseResult2
+ "]";
}
private static Matrix<N3, N3> orthogonalizeRotationMatrix(Matrix<N3, N3> input) {
var a = DecompositionFactory_DDRM.qr(3, 3);
if (!a.decompose(input.getStorage().getDDRM())) {
// best we can do is return the input
return input;
}
// Grab results (thanks for this _great_ api, EJML)
var Q = new DMatrixRMaj(3, 3);
var R = new DMatrixRMaj(3, 3);
a.getQ(Q, false);
a.getR(R, false);
// Fix signs in R if they're < 0 so it's close to an identity matrix
// (our QR decomposition implementation sometimes flips the signs of columns)
for (int colR = 0; colR < 3; ++colR) {
if (R.get(colR, colR) < 0) {
for (int rowQ = 0; rowQ < 3; ++rowQ) {
Q.set(rowQ, colR, -Q.get(rowQ, colR));
}
}
}
return new Matrix<>(new SimpleMatrix(Q));
}
}

View File

@@ -0,0 +1,18 @@
// 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;
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,107 @@
// 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("length").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();
}
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,320 @@
// 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 <wpi/jni_util.h>
#include "edu_wpi_first_apriltag_jni_AprilTagJNI.h"
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4200)
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#include "apriltag.h"
#ifdef _WIN32
#pragma warning(pop)
#endif
#include "tag36h11.h"
#include "tag25h9.h"
#include "tag16h5.h"
#include "tagCircle21h7.h"
#include "tagCircle49h12.h"
#include "tagCustom48h12.h"
#include "tagStandard41h12.h"
#include "tagStandard52h13.h"
#include "apriltag_pose.h"
#include <vector>
#include <algorithm>
#include <cmath>
using namespace wpi::java;
struct DetectorState {
int id;
apriltag_detector_t* td;
apriltag_family_t* tf;
void (*tf_destroy)(apriltag_family_t*);
};
static std::vector<DetectorState> detectors;
extern "C" {
/*
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
* Method: aprilTagCreate
* Signature: (Ljava/lang/String;DDIZZ)J
*/
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagCreate
(JNIEnv* env, jclass cls, jstring jstr, jdouble decimate, jdouble blur,
jint threads, jboolean debug, jboolean refine_edges)
{
// Initialize tag detector with options
apriltag_family_t* tf = nullptr;
// const char *famname = fam;
const char* famname = env->GetStringUTFChars(jstr, nullptr);
void (*tf_destroy_func)(apriltag_family_t*);
if (!strcmp(famname, "tag36h11")) {
tf = tag36h11_create();
tf_destroy_func = tag36h11_destroy;
} else if (!strcmp(famname, "tag25h9")) {
tf = tag25h9_create();
tf_destroy_func = tag25h9_destroy;
} else if (!strcmp(famname, "tag16h5")) {
tf = tag16h5_create();
tf_destroy_func = tag16h5_destroy;
} else if (!strcmp(famname, "tagCircle21h7")) {
tf = tagCircle21h7_create();
tf_destroy_func = tagCircle21h7_destroy;
} else if (!strcmp(famname, "tagCircle49h12")) {
tf = tagCircle49h12_create();
tf_destroy_func = tagCircle49h12_destroy;
} else if (!strcmp(famname, "tagStandard41h12")) {
tf = tagStandard41h12_create();
tf_destroy_func = tagStandard41h12_destroy;
} else if (!strcmp(famname, "tagStandard52h13")) {
tf = tagStandard52h13_create();
tf_destroy_func = tagStandard52h13_destroy;
} else if (!strcmp(famname, "tagCustom48h12")) {
tf = tagCustom48h12_create();
tf_destroy_func = tagCustom48h12_destroy;
} else {
std::printf("Unrecognized tag family name. Use e.g. \"tag36h11\".\n");
env->ReleaseStringUTFChars(jstr, famname);
return 0;
}
apriltag_detector_t* td = apriltag_detector_create();
apriltag_detector_add_family(td, tf);
td->quad_decimate = static_cast<float>(decimate);
td->quad_sigma = static_cast<float>(blur);
td->nthreads = threads;
td->debug = debug;
td->refine_edges = refine_edges;
env->ReleaseStringUTFChars(jstr, famname);
// std::printf("Looking for max\n");
auto max = std::max_element(detectors.begin(), detectors.end(),
[](DetectorState& a, DetectorState& b) {
return a.id < b.id;
}); // detectors.size();
int index = 0;
if (max != detectors.end())
index = max->id + 1;
detectors.push_back({index, td, tf, tf_destroy_func});
std::printf("Created detector at idx %i\n", index);
return (jlong)index;
}
static JClass detectionClass;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
detectionClass = JClass(env, "edu/wpi/first/apriltag/jni/DetectionResult");
if (!detectionClass) {
std::printf("Couldn't find class!");
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
static jobject MakeJObject(JNIEnv* env, const apriltag_detection_t* detect,
apriltag_pose_t& pose1, apriltag_pose_t& pose2,
double error1, double error2) {
// Constructor signature must match Java! I = int, F = float, [D = double
// array
static jmethodID constructor =
env->GetMethodID(detectionClass, "<init>", "(IIF[DDD[D[D[DD[D[DD)V");
if (!constructor) {
return nullptr;
}
if (!detect) {
return nullptr;
}
// We have to copy the homography matrix and coners into jdoubles
jdouble h[9]; // = new jdouble[9]{};
for (int i = 0; i < 9; i++) {
h[i] = detect->H->data[i];
}
jdouble corners[8]; // = new jdouble[8]{};
for (int i = 0; i < 4; i++) {
corners[i * 2] = detect->p[i][0];
corners[i * 2 + 1] = detect->p[i][1];
}
jdoubleArray harr = MakeJDoubleArray(env, {h, 9});
jdoubleArray carr = MakeJDoubleArray(env, {corners, 8});
// The rotation of the target is encoded as a 3 by 3 rotation matrix, we'll
// convert to a row-major array
jdouble pose1RotMat[9] = {0};
jdouble pose2RotMat[9] = {0};
for (int i = 0; i < 9; i++) {
if (pose1.R) {
pose1RotMat[i] = pose1.R->data[i];
}
if (pose2.R) {
pose2RotMat[i] = pose2.R->data[i];
}
}
// And translation a 3x1 vector (todo check axis order)
jdouble pose1Trans[3] = {0};
jdouble pose2Trans[3] = {0};
for (int i = 0; i < 3; i++) {
if (pose1.t) {
pose1Trans[i] = pose1.t->data[i];
}
if (pose2.t) {
pose2Trans[i] = pose2.t->data[i];
}
}
jdoubleArray pose1rotArr = MakeJDoubleArray(env, {pose1RotMat, 9});
jdoubleArray pose2rotArr = MakeJDoubleArray(env, {pose2RotMat, 9});
jdoubleArray pose1transArr = MakeJDoubleArray(env, {pose1Trans, 3});
jdoubleArray pose2transArr = MakeJDoubleArray(env, {pose2Trans, 3});
jdouble err1 = error1;
jdouble err2 = error2;
// Actually call the constructor
auto ret = env->NewObject(
detectionClass, constructor, (jint)detect->id, (jint)detect->hamming,
(jfloat)detect->decision_margin, harr, (jdouble)detect->c[0],
(jdouble)detect->c[1], carr, pose1transArr, pose1rotArr, err1,
pose2transArr, pose2rotArr, err2);
// TODO we don't seem to need this... or at least, it doesnt leak rn
// env->ReleaseDoubleArrayElements(harr, h, 0);
// env->ReleaseDoubleArrayElements(carr, corners, 0);
return ret;
}
/*
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
* Method: aprilTagDetectInternal
* Signature: (JJIIZDDDDDI)[Ljava/lang/Object;
*/
JNIEXPORT jobjectArray JNICALL
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagDetectInternal
(JNIEnv* env, jclass cls, jlong detectIdx, jlong pData, jint rows, jint cols,
jboolean doPoseEstimation, jdouble tagWidthMeters, jdouble fx, jdouble fy,
jdouble cx, jdouble cy, jint nIters)
{
// No image, can't do anything
if (!pData) {
return nullptr;
}
// Make an image_u8_t header for the Mat data
image_u8_t im = {static_cast<int32_t>(cols), static_cast<int32_t>(rows),
static_cast<int32_t>(cols),
reinterpret_cast<uint8_t*>(pData)};
// Get our detector
auto state =
std::find_if(detectors.begin(), detectors.end(),
[&](DetectorState& s) { return s.id == detectIdx; });
if (state == detectors.end()) {
return nullptr;
}
// And run the detector on our new image
zarray_t* detections = apriltag_detector_detect(state->td, &im);
int size = zarray_size(detections);
// Object array to return to Java
jobjectArray jarr = env->NewObjectArray(size, detectionClass, nullptr);
if (!jarr) {
std::printf("Couldn't make array\n");
return nullptr;
}
// Global pose
apriltag_pose_t pose1;
std::memset(&pose1, 0, sizeof(pose1));
apriltag_pose_t pose2;
std::memset(&pose2, 0, sizeof(pose2));
// std::printf("Created array %llu! Got %i targets!\n", &jarr, size);
// Add our detected targets to the array
for (int i = 0; i < size; ++i) {
apriltag_detection_t* det = nullptr;
zarray_get(detections, i, &det);
if (det != nullptr) {
double err1 =
HUGE_VAL; // Should get overwritten if pose estimation is happening
double err2 = HUGE_VAL;
if (doPoseEstimation) {
// Feed results to the pose estimator
apriltag_detection_info_t info{det, tagWidthMeters, fx, fy, cx, cy};
estimate_tag_pose_orthogonal_iteration(&info, &err1, &pose1, &err2,
&pose2, nIters);
}
jobject obj = MakeJObject(env, det, pose1, pose2, err1, err2);
env->SetObjectArrayElement(jarr, i, obj);
}
}
// Now that stuff's in our Java-side array, we can clean up native memory
apriltag_detections_destroy(detections);
return jarr;
}
/*
* Class: edu_wpi_first_apriltag_jni_AprilTagJNI
* Method: aprilTagDestroy
* Signature: (J)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_apriltag_jni_AprilTagJNI_aprilTagDestroy
(JNIEnv* env, jclass clazz, jlong detectIdx)
{
auto state =
std::find_if(detectors.begin(), detectors.end(),
[&](DetectorState& s) { return s.id == detectIdx; });
if (state == detectors.end()) {
return;
}
if (state->td) {
apriltag_detector_destroy(state->td);
state->td = nullptr;
}
if (state->tf) {
state->tf_destroy(state->tf);
state->tf = nullptr;
}
detectors.erase(detectors.begin() + detectIdx);
}
} // extern "C"

View File

@@ -0,0 +1,34 @@
// 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.
*/
bool operator==(const AprilTag&) const = default;
};
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,125 @@
// 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 in the JSON are measured using the normal FRC coordinate system, NWU
* with the origin at the bottom-right corner of the blue alliance wall.
* SetOrigin(OriginPosition) can be used to change the poses returned from
* GetTagPose(int) to be from the perspective of a specific alliance. */
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.
*/
bool operator==(const AprilTagFieldLayout&) const = default;
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" : 16.4592,
"width" : 8.2296
}
}

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,16 @@
// 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.jni;
import org.junit.jupiter.api.Test;
public class JNITest {
@Test
void jniLinkTest() {
// Test to verify that the JNI test link works correctly.
var detector = AprilTagJNI.aprilTagCreate("tag16h5", 2.0, 0.0, 1, false, false);
AprilTagJNI.aprilTagDestroy(detector);
}
}

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

@@ -2,12 +2,10 @@
// 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 "gtest/gtest.h"
class Switch {
public:
virtual ~Switch() = default;
/// \brief Returns true when the switch is triggered.
virtual bool Get() = 0;
};
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}

View File

@@ -15,7 +15,7 @@ stages:
vmImage: 'ubuntu-latest'
container:
image: wpilib/roborio-cross-ubuntu:2022-18.04
image: wpilib/roborio-cross-ubuntu:2023-22.04
timeoutInMinutes: 0
@@ -38,6 +38,7 @@ stages:
- stage: TestBench
displayName: Test Bench
condition: false
jobs:
- job: Cpp
displayName: C++

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.1'
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'
@@ -108,6 +108,11 @@ subprojects {
subproj.apply plugin: MultiBuilds
}
plugins.withType(JavaPlugin) {
sourceCompatibility = 11
targetCompatibility = 11
}
apply from: "${rootDir}/shared/java/javastyle.gradle"
// Disables doclint in java 8.
@@ -119,6 +124,10 @@ subprojects {
}
}
tasks.withType(JavaCompile) {
options.compilerArgs.add '-XDstringConcat=inline'
}
// Enables UTF-8 support in Javadoc
tasks.withType(Javadoc) {
options.addStringOption("charset", "utf-8")
@@ -127,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

@@ -1,9 +1,13 @@
repositories {
maven {
url "https://plugins.gradle.org/m2/"
mavenLocal()
maven {
url = 'https://frcmaven.wpi.edu/artifactory/ex-gradle'
}
mavenCentral()
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2023.1.0"
implementation "edu.wpi.first:native-utils:2023.9.0"
}

View File

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

View File

@@ -57,7 +57,7 @@ model {
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
return
}
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':cscore', library: 'cscore', linkage: 'shared'
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'

View File

@@ -55,7 +55,7 @@ model {
}
binaries.all { binary ->
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
project(':ntcore').addNtcoreDependency(binary, 'static')
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'

View File

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

View File

@@ -5,6 +5,7 @@
#include <cstdio>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <networktables/NetworkTableInstance.h>
@@ -182,7 +183,8 @@ int main(int argc, char* argv[]) {
ntinst.StartServer();
} else {
fmt::print("Setting up NetworkTables client for team {}\n", team);
ntinst.StartClientTeam(team);
ntinst.StartClient4("multicameraserver");
ntinst.SetServerTeam(team);
}
// start cameras

View File

@@ -15,13 +15,18 @@ import edu.wpi.first.cscore.VideoException;
import edu.wpi.first.cscore.VideoListener;
import edu.wpi.first.cscore.VideoMode;
import edu.wpi.first.cscore.VideoMode.PixelFormat;
import edu.wpi.first.cscore.VideoProperty;
import edu.wpi.first.cscore.VideoSink;
import edu.wpi.first.cscore.VideoSource;
import edu.wpi.first.networktables.EntryListenerFlags;
import edu.wpi.first.networktables.BooleanEntry;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.IntegerEntry;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringArrayPublisher;
import edu.wpi.first.networktables.StringArrayTopic;
import edu.wpi.first.networktables.StringEntry;
import edu.wpi.first.networktables.StringPublisher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -38,11 +43,167 @@ public final class CameraServer {
private static final String kPublishName = "/CameraPublisher";
private static final class PropertyPublisher implements AutoCloseable {
@SuppressWarnings({"PMD.MissingBreakInSwitch", "PMD.ImplicitSwitchFallThrough", "fallthrough"})
PropertyPublisher(NetworkTable table, VideoEvent event) {
String name;
String infoName;
if (event.name.startsWith("raw_")) {
name = "RawProperty/" + event.name;
infoName = "RawPropertyInfo/" + event.name;
} else {
name = "Property/" + event.name;
infoName = "PropertyInfo/" + event.name;
}
try {
switch (event.propertyKind) {
case kBoolean:
m_booleanValueEntry = table.getBooleanTopic(name).getEntry(false);
m_booleanValueEntry.setDefault(event.value != 0);
break;
case kEnum:
m_choicesTopic = table.getStringArrayTopic(infoName + "/choices");
// fall through
case kInteger:
m_integerValueEntry = table.getIntegerTopic(name).getEntry(0);
m_minPublisher = table.getIntegerTopic(infoName + "/min").publish();
m_maxPublisher = table.getIntegerTopic(infoName + "/max").publish();
m_stepPublisher = table.getIntegerTopic(infoName + "/step").publish();
m_defaultPublisher = table.getIntegerTopic(infoName + "/default").publish();
m_integerValueEntry.setDefault(event.value);
m_minPublisher.set(CameraServerJNI.getPropertyMin(event.propertyHandle));
m_maxPublisher.set(CameraServerJNI.getPropertyMax(event.propertyHandle));
m_stepPublisher.set(CameraServerJNI.getPropertyStep(event.propertyHandle));
m_defaultPublisher.set(CameraServerJNI.getPropertyDefault(event.propertyHandle));
break;
case kString:
m_stringValueEntry = table.getStringTopic(name).getEntry("");
m_stringValueEntry.setDefault(event.valueStr);
break;
default:
break;
}
} catch (VideoException ignored) {
// ignore
}
}
void update(VideoEvent event) {
switch (event.propertyKind) {
case kBoolean:
if (m_booleanValueEntry != null) {
m_booleanValueEntry.set(event.value != 0);
}
break;
case kInteger:
case kEnum:
if (m_integerValueEntry != null) {
m_integerValueEntry.set(event.value);
}
break;
case kString:
if (m_stringValueEntry != null) {
m_stringValueEntry.set(event.valueStr);
}
break;
default:
break;
}
}
@Override
public void close() throws Exception {
if (m_booleanValueEntry != null) {
m_booleanValueEntry.close();
}
if (m_integerValueEntry != null) {
m_integerValueEntry.close();
}
if (m_stringValueEntry != null) {
m_stringValueEntry.close();
}
if (m_minPublisher != null) {
m_minPublisher.close();
}
if (m_maxPublisher != null) {
m_maxPublisher.close();
}
if (m_stepPublisher != null) {
m_stepPublisher.close();
}
if (m_defaultPublisher != null) {
m_defaultPublisher.close();
}
if (m_choicesPublisher != null) {
m_choicesPublisher.close();
}
}
BooleanEntry m_booleanValueEntry;
IntegerEntry m_integerValueEntry;
StringEntry m_stringValueEntry;
IntegerPublisher m_minPublisher;
IntegerPublisher m_maxPublisher;
IntegerPublisher m_stepPublisher;
IntegerPublisher m_defaultPublisher;
StringArrayTopic m_choicesTopic;
StringArrayPublisher m_choicesPublisher;
}
private static final class SourcePublisher implements AutoCloseable {
SourcePublisher(NetworkTable table, int sourceHandle) {
this.m_table = table;
m_sourcePublisher = table.getStringTopic("source").publish();
m_descriptionPublisher = table.getStringTopic("description").publish();
m_connectedPublisher = table.getBooleanTopic("connected").publish();
m_streamsPublisher = table.getStringArrayTopic("streams").publish();
m_modeEntry = table.getStringTopic("mode").getEntry("");
m_modesPublisher = table.getStringArrayTopic("modes").publish();
m_sourcePublisher.set(makeSourceValue(sourceHandle));
m_descriptionPublisher.set(CameraServerJNI.getSourceDescription(sourceHandle));
m_connectedPublisher.set(CameraServerJNI.isSourceConnected(sourceHandle));
m_streamsPublisher.set(getSourceStreamValues(sourceHandle));
try {
VideoMode mode = CameraServerJNI.getSourceVideoMode(sourceHandle);
m_modeEntry.setDefault(videoModeToString(mode));
m_modesPublisher.set(getSourceModeValues(sourceHandle));
} catch (VideoException ignored) {
// Do nothing. Let the other event handlers update this if there is an error.
}
}
@Override
public void close() throws Exception {
m_sourcePublisher.close();
m_descriptionPublisher.close();
m_connectedPublisher.close();
m_streamsPublisher.close();
m_modeEntry.close();
m_modesPublisher.close();
for (PropertyPublisher pp : m_properties.values()) {
pp.close();
}
}
final NetworkTable m_table;
final StringPublisher m_sourcePublisher;
final StringPublisher m_descriptionPublisher;
final BooleanPublisher m_connectedPublisher;
final StringArrayPublisher m_streamsPublisher;
final StringEntry m_modeEntry;
final StringArrayPublisher m_modesPublisher;
final Map<Integer, PropertyPublisher> m_properties = new HashMap<>();
}
private static final AtomicInteger m_defaultUsbDevice = new AtomicInteger();
private static String m_primarySourceName;
private static final Map<String, VideoSource> m_sources = new HashMap<>();
private static final Map<String, VideoSink> m_sinks = new HashMap<>();
private static final Map<Integer, NetworkTable> m_tables =
private static final Map<Integer, SourcePublisher> m_publishers =
new HashMap<>(); // indexed by source handle
// source handle indexed by sink handle
private static final Map<Integer, Integer> m_fixedSources = new HashMap<>();
@@ -61,189 +222,124 @@ public final class CameraServer {
// - "PropertyInfo/{Property}" - Property supporting information
// Listener for video events
@SuppressWarnings("PMD.UnusedPrivateField")
@SuppressWarnings({"PMD.UnusedPrivateField", "PMD.AvoidCatchingGenericException"})
private static final VideoListener m_videoListener =
new VideoListener(
event -> {
switch (event.kind) {
case kSourceCreated:
{
// Create subtable for the camera
NetworkTable table = m_publishTable.getSubTable(event.name);
m_tables.put(event.sourceHandle, table);
table.getEntry("source").setString(makeSourceValue(event.sourceHandle));
table
.getEntry("description")
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
table
.getEntry("connected")
.setBoolean(CameraServerJNI.isSourceConnected(event.sourceHandle));
table
.getEntry("streams")
.setStringArray(getSourceStreamValues(event.sourceHandle));
try {
VideoMode mode = CameraServerJNI.getSourceVideoMode(event.sourceHandle);
table.getEntry("mode").setDefaultString(videoModeToString(mode));
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
} catch (VideoException ignored) {
// Do nothing. Let the other event handlers update this if there is an error.
synchronized (CameraServer.class) {
switch (event.kind) {
case kSourceCreated:
{
// Create subtable for the camera
NetworkTable table = m_publishTable.getSubTable(event.name);
m_publishers.put(
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
break;
}
break;
}
case kSourceDestroyed:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
table.getEntry("source").setString("");
table.getEntry("streams").setStringArray(new String[0]);
table.getEntry("modes").setStringArray(new String[0]);
}
break;
}
case kSourceConnected:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
// update the description too (as it may have changed)
table
.getEntry("description")
.setString(CameraServerJNI.getSourceDescription(event.sourceHandle));
table.getEntry("connected").setBoolean(true);
}
break;
}
case kSourceDisconnected:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
table.getEntry("connected").setBoolean(false);
}
break;
}
case kSourceVideoModesUpdated:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
table.getEntry("modes").setStringArray(getSourceModeValues(event.sourceHandle));
}
break;
}
case kSourceVideoModeChanged:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
table.getEntry("mode").setString(videoModeToString(event.mode));
}
break;
}
case kSourcePropertyCreated:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
putSourcePropertyValue(table, event, true);
}
break;
}
case kSourcePropertyValueUpdated:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
putSourcePropertyValue(table, event, false);
}
break;
}
case kSourcePropertyChoicesUpdated:
{
NetworkTable table = m_tables.get(event.sourceHandle);
if (table != null) {
try {
String[] choices =
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
table
.getEntry("PropertyInfo/" + event.name + "/choices")
.setStringArray(choices);
} catch (VideoException ignored) {
// ignore
case kSourceDestroyed:
{
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
if (publisher != null) {
try {
publisher.close();
} catch (Exception e) {
// ignore (nothing we can do about it)
}
}
break;
}
case kSourceConnected:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
// update the description too (as it may have changed)
publisher.m_descriptionPublisher.set(
CameraServerJNI.getSourceDescription(event.sourceHandle));
publisher.m_connectedPublisher.set(true);
}
break;
}
case kSourceDisconnected:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_connectedPublisher.set(false);
}
break;
}
case kSourceVideoModesUpdated:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
}
break;
}
case kSourceVideoModeChanged:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_modeEntry.set(videoModeToString(event.mode));
}
break;
}
case kSourcePropertyCreated:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_properties.put(
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
}
break;
}
case kSourcePropertyValueUpdated:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
if (pp != null) {
pp.update(event);
}
}
break;
}
case kSourcePropertyChoicesUpdated:
{
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
if (pp != null && pp.m_choicesTopic != null) {
try {
String[] choices =
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
if (pp.m_choicesPublisher == null) {
pp.m_choicesPublisher = pp.m_choicesTopic.publish();
}
pp.m_choicesPublisher.set(choices);
} catch (VideoException ignored) {
// ignore (just don't publish choices if we can't get them)
}
}
}
break;
}
case kSinkSourceChanged:
case kSinkCreated:
case kSinkDestroyed:
case kNetworkInterfacesChanged:
{
m_addresses = CameraServerJNI.getNetworkInterfaces();
updateStreamValues();
break;
}
default:
break;
}
case kSinkSourceChanged:
case kSinkCreated:
case kSinkDestroyed:
case kNetworkInterfacesChanged:
{
m_addresses = CameraServerJNI.getNetworkInterfaces();
updateStreamValues();
break;
}
default:
break;
}
}
},
0x4fff,
true);
@SuppressWarnings("PMD.UnusedPrivateField")
private static final int m_tableListener =
NetworkTableInstance.getDefault()
.addEntryListener(
kPublishName + "/",
event -> {
String relativeKey = event.name.substring(kPublishName.length() + 1);
// get source (sourceName/...)
int subKeyIndex = relativeKey.indexOf('/');
if (subKeyIndex == -1) {
return;
}
String sourceName = relativeKey.substring(0, subKeyIndex);
VideoSource source = m_sources.get(sourceName);
if (source == null) {
return;
}
// get subkey
relativeKey = relativeKey.substring(subKeyIndex + 1);
// handle standard names
String propName;
if ("mode".equals(relativeKey)) {
// reset to current mode
event.getEntry().setString(videoModeToString(source.getVideoMode()));
return;
} else if (relativeKey.startsWith("Property/")) {
propName = relativeKey.substring(9);
} else if (relativeKey.startsWith("RawProperty/")) {
propName = relativeKey.substring(12);
} else {
return; // ignore
}
// everything else is a property
VideoProperty property = source.getProperty(propName);
switch (property.getKind()) {
case kNone:
return;
case kBoolean:
// reset to current setting
event.getEntry().setBoolean(property.get() != 0);
return;
case kInteger:
case kEnum:
// reset to current setting
event.getEntry().setDouble(property.get());
return;
case kString:
// reset to current setting
event.getEntry().setString(property.getString());
return;
default:
return;
}
},
EntryListenerFlags.kImmediate | EntryListenerFlags.kUpdate);
private static int m_nextPort = kBasePort;
private static String[] m_addresses = new String[0];
@@ -369,8 +465,8 @@ public final class CameraServer {
if (source == 0) {
continue;
}
NetworkTable table = m_tables.get(source);
if (table != null) {
SourcePublisher publisher = m_publishers.get(source);
if (publisher != null) {
// Don't set stream values if this is a HttpCamera passthrough
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
== VideoSource.Kind.kHttp) {
@@ -380,7 +476,7 @@ public final class CameraServer {
// Set table value
String[] values = getSinkStreamValues(sink);
if (values.length > 0) {
table.getEntry("streams").setStringArray(values);
publisher.m_streamsPublisher.set(values);
}
}
}
@@ -390,12 +486,12 @@ public final class CameraServer {
int source = i.getHandle();
// Get the source's subtable (if none exists, we're done)
NetworkTable table = m_tables.get(source);
if (table != null) {
SourcePublisher publisher = m_publishers.get(source);
if (publisher != null) {
// Set table value
String[] values = getSourceStreamValues(source);
if (values.length > 0) {
table.getEntry("streams").setStringArray(values);
publisher.m_streamsPublisher.set(values);
}
}
}
@@ -449,69 +545,6 @@ public final class CameraServer {
return modeStrings;
}
/**
* Publish a source property value to NetworkTables.
*
* @param table NetworkTable to which to push value.
* @param event Video event.
* @param isNew Whether the property value hasn't been pushed to NetworkTables before.
*/
private static void putSourcePropertyValue(NetworkTable table, VideoEvent event, boolean isNew) {
String name;
String infoName;
if (event.name.startsWith("raw_")) {
name = "RawProperty/" + event.name;
infoName = "RawPropertyInfo/" + event.name;
} else {
name = "Property/" + event.name;
infoName = "PropertyInfo/" + event.name;
}
NetworkTableEntry entry = table.getEntry(name);
try {
switch (event.propertyKind) {
case kBoolean:
if (isNew) {
entry.setDefaultBoolean(event.value != 0);
} else {
entry.setBoolean(event.value != 0);
}
break;
case kInteger:
case kEnum:
if (isNew) {
entry.setDefaultDouble(event.value);
table
.getEntry(infoName + "/min")
.setDouble(CameraServerJNI.getPropertyMin(event.propertyHandle));
table
.getEntry(infoName + "/max")
.setDouble(CameraServerJNI.getPropertyMax(event.propertyHandle));
table
.getEntry(infoName + "/step")
.setDouble(CameraServerJNI.getPropertyStep(event.propertyHandle));
table
.getEntry(infoName + "/default")
.setDouble(CameraServerJNI.getPropertyDefault(event.propertyHandle));
} else {
entry.setDouble(event.value);
}
break;
case kString:
if (isNew) {
entry.setDefaultString(event.valueStr);
} else {
entry.setString(event.valueStr);
}
break;
default:
break;
}
} catch (VideoException ignored) {
// ignore
}
}
private CameraServer() {}
/**

View File

@@ -8,8 +8,12 @@
#include <vector>
#include <fmt/format.h>
#include <networktables/BooleanTopic.h>
#include <networktables/IntegerTopic.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/StringArrayTopic.h>
#include <networktables/StringTopic.h>
#include <wpi/DenseMap.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
@@ -24,9 +28,42 @@ using namespace frc;
static constexpr char const* kPublishName = "/CameraPublisher";
namespace {
struct Instance;
struct PropertyPublisher {
PropertyPublisher(nt::NetworkTable& table, const cs::VideoEvent& event);
void Update(const cs::VideoEvent& event);
nt::BooleanEntry booleanValueEntry;
nt::IntegerEntry integerValueEntry;
nt::StringEntry stringValueEntry;
nt::IntegerPublisher minPublisher;
nt::IntegerPublisher maxPublisher;
nt::IntegerPublisher stepPublisher;
nt::IntegerPublisher defaultPublisher;
nt::StringArrayTopic choicesTopic;
nt::StringArrayPublisher choicesPublisher;
};
struct SourcePublisher {
SourcePublisher(Instance& inst, std::shared_ptr<nt::NetworkTable> table,
CS_Source source);
std::shared_ptr<nt::NetworkTable> table;
nt::StringPublisher sourcePublisher;
nt::StringPublisher descriptionPublisher;
nt::BooleanPublisher connectedPublisher;
nt::StringArrayPublisher streamsPublisher;
nt::StringEntry modeEntry;
nt::StringArrayPublisher modesPublisher;
wpi::DenseMap<CS_Property, PropertyPublisher> properties;
};
struct Instance {
Instance();
std::shared_ptr<nt::NetworkTable> GetSourceTable(CS_Source source);
SourcePublisher* GetPublisher(CS_Source source);
std::vector<std::string> GetSinkStreamValues(CS_Sink sink);
std::vector<std::string> GetSourceStreamValues(CS_Source source);
void UpdateStreamValues();
@@ -37,7 +74,7 @@ struct Instance {
wpi::StringMap<cs::VideoSource> m_sources;
wpi::StringMap<cs::VideoSink> m_sinks;
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
wpi::DenseMap<CS_Source, SourcePublisher> m_publishers;
std::shared_ptr<nt::NetworkTable> m_publishTable{
nt::NetworkTableInstance::GetDefault().GetTable(kPublishName)};
cs::VideoListener m_videoListener;
@@ -45,6 +82,7 @@ struct Instance {
int m_nextPort{CameraServer::kBasePort};
std::vector<std::string> m_addresses;
};
} // namespace
static Instance& GetInstance() {
@@ -86,9 +124,13 @@ static std::string MakeStreamValue(std::string_view address, int port) {
return fmt::format("mjpg:http://{}:{}/?action=stream", address, port);
}
std::shared_ptr<nt::NetworkTable> Instance::GetSourceTable(CS_Source source) {
std::scoped_lock lock(m_mutex);
return m_tables.lookup(source);
SourcePublisher* Instance::GetPublisher(CS_Source source) {
auto it = m_publishers.find(source);
if (it != m_publishers.end()) {
return &it->second;
} else {
return nullptr;
}
}
std::vector<std::string> Instance::GetSinkStreamValues(CS_Sink sink) {
@@ -158,7 +200,6 @@ std::vector<std::string> Instance::GetSourceStreamValues(CS_Source source) {
}
void Instance::UpdateStreamValues() {
std::scoped_lock lock(m_mutex);
// Over all the sinks...
for (const auto& i : m_sinks) {
CS_Status status = 0;
@@ -172,8 +213,7 @@ void Instance::UpdateStreamValues() {
if (source == 0) {
continue;
}
auto table = m_tables.lookup(source);
if (table) {
if (auto publisher = GetPublisher(source)) {
// Don't set stream values if this is a HttpCamera passthrough
if (cs::GetSourceKind(source, &status) == CS_SOURCE_HTTP) {
continue;
@@ -182,7 +222,7 @@ void Instance::UpdateStreamValues() {
// Set table value
auto values = GetSinkStreamValues(sink);
if (!values.empty()) {
table->GetEntry("streams").SetStringArray(values);
publisher->streamsPublisher.Set(values);
}
}
}
@@ -192,12 +232,11 @@ void Instance::UpdateStreamValues() {
CS_Source source = i.second.GetHandle();
// Get the source's subtable (if none exists, we're done)
auto table = m_tables.lookup(source);
if (table) {
if (auto publisher = GetPublisher(source)) {
// Set table value
auto values = GetSourceStreamValues(source);
if (!values.empty()) {
table->GetEntry("streams").SetStringArray(values);
publisher->streamsPublisher.Set(values);
}
}
}
@@ -234,51 +273,71 @@ static std::vector<std::string> GetSourceModeValues(int source) {
return rv;
}
static void PutSourcePropertyValue(nt::NetworkTable* table,
const cs::VideoEvent& event, bool isNew) {
std::string_view namePrefix;
std::string_view infoPrefix;
PropertyPublisher::PropertyPublisher(nt::NetworkTable& table,
const cs::VideoEvent& event) {
std::string name;
std::string infoName;
if (wpi::starts_with(event.name, "raw_")) {
namePrefix = "RawProperty";
infoPrefix = "RawPropertyInfo";
name = fmt::format("RawProperty/{}", event.name);
infoName = fmt::format("RawPropertyInfo/{}", event.name);
} else {
namePrefix = "Property";
infoPrefix = "PropertyInfo";
name = fmt::format("Property/{}", event.name);
infoName = fmt::format("PropertyInfo/{}", event.name);
}
wpi::SmallString<64> buf;
CS_Status status = 0;
nt::NetworkTableEntry entry =
table->GetEntry(fmt::format("{}/{}", namePrefix, event.name));
switch (event.propertyKind) {
case CS_PROP_BOOLEAN:
if (isNew) {
entry.SetDefaultBoolean(event.value != 0);
} else {
entry.SetBoolean(event.value != 0);
booleanValueEntry = table.GetBooleanTopic(name).GetEntry(false);
booleanValueEntry.SetDefault(event.value != 0);
break;
case CS_PROP_ENUM:
choicesTopic =
table.GetStringArrayTopic(fmt::format("{}/choices", infoName));
[[fallthrough]];
case CS_PROP_INTEGER:
integerValueEntry = table.GetIntegerTopic(name).GetEntry(0);
minPublisher =
table.GetIntegerTopic(fmt::format("{}/min", infoName)).Publish();
maxPublisher =
table.GetIntegerTopic(fmt::format("{}/max", infoName)).Publish();
stepPublisher =
table.GetIntegerTopic(fmt::format("{}/step", infoName)).Publish();
defaultPublisher =
table.GetIntegerTopic(fmt::format("{}/default", infoName)).Publish();
integerValueEntry.SetDefault(event.value);
minPublisher.Set(cs::GetPropertyMin(event.propertyHandle, &status));
maxPublisher.Set(cs::GetPropertyMax(event.propertyHandle, &status));
stepPublisher.Set(cs::GetPropertyStep(event.propertyHandle, &status));
defaultPublisher.Set(
cs::GetPropertyDefault(event.propertyHandle, &status));
break;
case CS_PROP_STRING:
stringValueEntry = table.GetStringTopic(name).GetEntry("");
stringValueEntry.SetDefault(event.valueStr);
break;
default:
break;
}
}
void PropertyPublisher::Update(const cs::VideoEvent& event) {
switch (event.propertyKind) {
case CS_PROP_BOOLEAN:
if (booleanValueEntry) {
booleanValueEntry.Set(event.value != 0);
}
break;
case CS_PROP_INTEGER:
case CS_PROP_ENUM:
if (isNew) {
entry.SetDefaultDouble(event.value);
table->GetEntry(fmt::format("{}/{}/min", infoPrefix, event.name))
.SetDouble(cs::GetPropertyMin(event.propertyHandle, &status));
table->GetEntry(fmt::format("{}/{}/max", infoPrefix, event.name))
.SetDouble(cs::GetPropertyMax(event.propertyHandle, &status));
table->GetEntry(fmt::format("{}/{}/step", infoPrefix, event.name))
.SetDouble(cs::GetPropertyStep(event.propertyHandle, &status));
table->GetEntry(fmt::format("{}/{}/default", infoPrefix, event.name))
.SetDouble(cs::GetPropertyDefault(event.propertyHandle, &status));
} else {
entry.SetDouble(event.value);
if (integerValueEntry) {
integerValueEntry.Set(event.value);
}
break;
case CS_PROP_STRING:
if (isNew) {
entry.SetDefaultString(event.valueStr);
} else {
entry.SetString(event.valueStr);
if (stringValueEntry) {
stringValueEntry.Set(event.valueStr);
}
break;
default:
@@ -286,6 +345,28 @@ static void PutSourcePropertyValue(nt::NetworkTable* table,
}
}
SourcePublisher::SourcePublisher(Instance& inst,
std::shared_ptr<nt::NetworkTable> table,
CS_Source source)
: table{table},
sourcePublisher{table->GetStringTopic("source").Publish()},
descriptionPublisher{table->GetStringTopic("description").Publish()},
connectedPublisher{table->GetBooleanTopic("connected").Publish()},
streamsPublisher{table->GetStringArrayTopic("streams").Publish()},
modeEntry{table->GetStringTopic("mode").GetEntry("")},
modesPublisher{table->GetStringArrayTopic("modes").Publish()} {
CS_Status status = 0;
wpi::SmallString<64> buf;
sourcePublisher.Set(MakeSourceValue(source, buf));
wpi::SmallString<64> descBuf;
descriptionPublisher.Set(cs::GetSourceDescription(source, descBuf, &status));
connectedPublisher.Set(cs::IsSourceConnected(source, &status));
streamsPublisher.Set(inst.GetSourceStreamValues(source));
auto mode = cs::GetSourceVideoMode(source, &status);
modeEntry.SetDefault(VideoModeToString(mode));
modesPublisher.Set(GetSourceModeValues(source));
}
Instance::Instance() {
// We publish sources to NetworkTables using the following structure:
// "/CameraPublisher/{Source.Name}/" - root
@@ -300,177 +381,88 @@ Instance::Instance() {
// Listener for video events
m_videoListener = cs::VideoListener{
[=](const cs::VideoEvent& event) {
[=, this](const cs::VideoEvent& event) {
std::scoped_lock lock(m_mutex);
CS_Status status = 0;
switch (event.kind) {
case cs::VideoEvent::kSourceCreated: {
// Create subtable for the camera
auto table = m_publishTable->GetSubTable(event.name);
{
std::scoped_lock lock(m_mutex);
m_tables.insert(std::make_pair(event.sourceHandle, table));
}
wpi::SmallString<64> buf;
table->GetEntry("source").SetString(
MakeSourceValue(event.sourceHandle, buf));
wpi::SmallString<64> descBuf;
table->GetEntry("description")
.SetString(cs::GetSourceDescription(event.sourceHandle, descBuf,
&status));
table->GetEntry("connected")
.SetBoolean(cs::IsSourceConnected(event.sourceHandle, &status));
table->GetEntry("streams").SetStringArray(
GetSourceStreamValues(event.sourceHandle));
auto mode = cs::GetSourceVideoMode(event.sourceHandle, &status);
table->GetEntry("mode").SetDefaultString(VideoModeToString(mode));
table->GetEntry("modes").SetStringArray(
GetSourceModeValues(event.sourceHandle));
m_publishers.insert(
{event.sourceHandle,
SourcePublisher{*this, table, event.sourceHandle}});
break;
}
case cs::VideoEvent::kSourceDestroyed: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
table->GetEntry("source").SetString("");
table->GetEntry("streams").SetStringArray(
std::vector<std::string>{});
table->GetEntry("modes").SetStringArray(
std::vector<std::string>{});
}
case cs::VideoEvent::kSourceDestroyed:
m_publishers.erase(event.sourceHandle);
break;
}
case cs::VideoEvent::kSourceConnected: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
case cs::VideoEvent::kSourceConnected:
if (auto publisher = GetPublisher(event.sourceHandle)) {
// update the description too (as it may have changed)
wpi::SmallString<64> descBuf;
table->GetEntry("description")
.SetString(cs::GetSourceDescription(event.sourceHandle,
descBuf, &status));
table->GetEntry("connected").SetBoolean(true);
publisher->descriptionPublisher.Set(cs::GetSourceDescription(
event.sourceHandle, descBuf, &status));
publisher->connectedPublisher.Set(true);
}
break;
}
case cs::VideoEvent::kSourceDisconnected: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
table->GetEntry("connected").SetBoolean(false);
case cs::VideoEvent::kSourceDisconnected:
if (auto publisher = GetPublisher(event.sourceHandle)) {
publisher->connectedPublisher.Set(false);
}
break;
}
case cs::VideoEvent::kSourceVideoModesUpdated: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
table->GetEntry("modes").SetStringArray(
case cs::VideoEvent::kSourceVideoModesUpdated:
if (auto publisher = GetPublisher(event.sourceHandle)) {
publisher->modesPublisher.Set(
GetSourceModeValues(event.sourceHandle));
}
break;
}
case cs::VideoEvent::kSourceVideoModeChanged: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
table->GetEntry("mode").SetString(VideoModeToString(event.mode));
case cs::VideoEvent::kSourceVideoModeChanged:
if (auto publisher = GetPublisher(event.sourceHandle)) {
publisher->modeEntry.Set(VideoModeToString(event.mode));
}
break;
}
case cs::VideoEvent::kSourcePropertyCreated: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
PutSourcePropertyValue(table.get(), event, true);
case cs::VideoEvent::kSourcePropertyCreated:
if (auto publisher = GetPublisher(event.sourceHandle)) {
publisher->properties.insert(
{event.propertyHandle,
PropertyPublisher{*publisher->table, event}});
}
break;
}
case cs::VideoEvent::kSourcePropertyValueUpdated: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
PutSourcePropertyValue(table.get(), event, false);
case cs::VideoEvent::kSourcePropertyValueUpdated:
if (auto publisher = GetPublisher(event.sourceHandle)) {
auto ppIt = publisher->properties.find(event.propertyHandle);
if (ppIt != publisher->properties.end()) {
ppIt->second.Update(event);
}
}
break;
}
case cs::VideoEvent::kSourcePropertyChoicesUpdated: {
auto table = GetSourceTable(event.sourceHandle);
if (table) {
auto choices =
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
table
->GetEntry(fmt::format("PropertyInfo/{}/choices", event.name))
.SetStringArray(choices);
case cs::VideoEvent::kSourcePropertyChoicesUpdated:
if (auto publisher = GetPublisher(event.sourceHandle)) {
auto ppIt = publisher->properties.find(event.propertyHandle);
if (ppIt != publisher->properties.end() &&
ppIt->second.choicesTopic) {
auto choices =
cs::GetEnumPropertyChoices(event.propertyHandle, &status);
if (!ppIt->second.choicesPublisher) {
ppIt->second.choicesPublisher =
ppIt->second.choicesTopic.Publish();
}
ppIt->second.choicesPublisher.Set(choices);
}
}
break;
}
case cs::VideoEvent::kSinkSourceChanged:
case cs::VideoEvent::kSinkCreated:
case cs::VideoEvent::kSinkDestroyed:
case cs::VideoEvent::kNetworkInterfacesChanged: {
case cs::VideoEvent::kNetworkInterfacesChanged:
m_addresses = cs::GetNetworkInterfaces();
UpdateStreamValues();
break;
}
default:
break;
}
},
0x4fff, true};
// Listener for NetworkTable events
// We don't currently support changing settings via NT due to
// synchronization issues, so just update to current setting if someone
// else tries to change it.
wpi::SmallString<64> buf;
m_tableListener = nt::NetworkTableInstance::GetDefault().AddEntryListener(
fmt::format("{}/", kPublishName),
[=](const nt::EntryNotification& event) {
auto relativeKey = wpi::drop_front(
event.name, std::string_view{kPublishName}.size() + 1);
// get source (sourceName/...)
auto subKeyIndex = relativeKey.find('/');
if (subKeyIndex == std::string_view::npos) {
return;
}
auto sourceName = wpi::slice(relativeKey, 0, subKeyIndex);
auto sourceIt = m_sources.find(sourceName);
if (sourceIt == m_sources.end()) {
return;
}
// get subkey
relativeKey.remove_prefix(subKeyIndex + 1);
// handle standard names
std::string_view propName;
nt::NetworkTableEntry entry{event.entry};
if (relativeKey == "mode") {
// reset to current mode
entry.SetString(VideoModeToString(sourceIt->second.GetVideoMode()));
return;
} else if (wpi::starts_with(relativeKey, "Property/")) {
propName = wpi::substr(relativeKey, 9);
} else if (wpi::starts_with(relativeKey, "RawProperty/")) {
propName = wpi::substr(relativeKey, 12);
} else {
return; // ignore
}
// everything else is a property
auto property = sourceIt->second.GetProperty(propName);
switch (property.GetKind()) {
case cs::VideoProperty::kNone:
return;
case cs::VideoProperty::kBoolean:
entry.SetBoolean(property.Get() != 0);
return;
case cs::VideoProperty::kInteger:
case cs::VideoProperty::kEnum:
entry.SetDouble(property.Get());
return;
case cs::VideoProperty::kString:
entry.SetString(property.GetString());
return;
default:
return;
}
},
NT_NOTIFY_IMMEDIATE | NT_NOTIFY_UPDATE);
}
cs::UsbCamera CameraServer::StartAutomaticCapture() {
@@ -519,7 +511,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(const std::string& host) {
return AddAxisCamera("Axis Camera", host);
}
cs::AxisCamera CameraServer::AddAxisCamera(wpi::span<const std::string> hosts) {
cs::AxisCamera CameraServer::AddAxisCamera(std::span<const std::string> hosts) {
return AddAxisCamera("Axis Camera", hosts);
}
@@ -551,7 +543,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
}
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
wpi::span<const std::string> hosts) {
std::span<const std::string> hosts) {
cs::AxisCamera camera{name, hosts};
StartAutomaticCapture(camera);
auto csShared = GetCameraServerShared();

View File

@@ -6,11 +6,10 @@
#include <stdint.h>
#include <span>
#include <string>
#include <string_view>
#include <wpi/span.h>
#include "cscore.h"
#include "cscore_cv.h"
@@ -110,7 +109,7 @@ class CameraServer {
*
* @param hosts Array of Camera host IPs/DNS names
*/
static cs::AxisCamera AddAxisCamera(wpi::span<const std::string> hosts);
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
/**
* Adds an Axis IP camera.
@@ -155,7 +154,7 @@ class CameraServer {
* @param hosts Array of Camera host IPs/DNS names
*/
static cs::AxisCamera AddAxisCamera(std::string_view name,
wpi::span<const std::string> hosts);
std::span<const std::string> hosts);
/**
* Adds an Axis IP camera.

View File

@@ -49,6 +49,7 @@ class TestEnvironment : public testing::Environment {
HAL_GetControlWord(&controlWord);
return controlWord.enabled && controlWord.dsAttached;
};
HAL_RefreshDSData();
while (!checkEnabled()) {
if (enableCounter > 50) {
// Robot did not enable properly after 5 seconds.
@@ -60,6 +61,7 @@ class TestEnvironment : public testing::Environment {
std::this_thread::sleep_for(100ms);
fmt::print("Waiting for enable: {}\n", enableCounter++);
HAL_RefreshDSData();
}
std::this_thread::sleep_for(500ms);
}

View File

@@ -61,8 +61,13 @@ model {
if (!it.buildable || !(it instanceof NativeBinarySpec)) {
return
}
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
if (it.component.name == "${nativeName}JNI") {
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
} else {
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
}
}
}
}
@@ -176,11 +181,13 @@ nativeUtils.exportsConfigs {
}
}
apply from: "${rootDir}/shared/imgui.gradle"
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 {
@@ -188,8 +195,8 @@ model {
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
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) {
nativeUtils.useRequiredLibrary(it, 'imgui')
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -199,6 +206,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

@@ -13,7 +13,9 @@ public class VideoMode {
kYUYV(2),
kRGB565(3),
kBGR(4),
kGray(5);
kGray(5),
kY16(6),
kUYVY(7);
private final int value;

View File

@@ -88,7 +88,7 @@ int ConfigurableSourceImpl::CreateProperty(
}
void ConfigurableSourceImpl::SetEnumPropertyChoices(
int property, wpi::span<const std::string> choices, CS_Status* status) {
int property, std::span<const std::string> choices, CS_Status* status) {
std::scoped_lock lock(m_mutex);
auto prop = GetProperty(property);
if (!prop) {

View File

@@ -8,12 +8,11 @@
#include <atomic>
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/span.h>
#include "SourceImpl.h"
namespace cs {
@@ -42,7 +41,7 @@ class ConfigurableSourceImpl : public SourceImpl {
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange);
void SetEnumPropertyChoices(int property,
wpi::span<const std::string> choices,
std::span<const std::string> choices,
CS_Status* status);
private:

View File

@@ -110,7 +110,7 @@ void CvSinkImpl::ThreadMain() {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
SDEBUG4("{}", "waiting for frame");
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(); // blocks
if (!m_active) {
break;

View File

@@ -139,7 +139,7 @@ CS_Property CreateSourcePropertyCallback(
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
wpi::span<const std::string> choices,
std::span<const std::string> choices,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {

View File

@@ -217,7 +217,7 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
// Color convert
switch (pixelFormat) {
case VideoMode::kRGB565:
// If source is YUYV or Gray, need to convert to BGR first
// If source is YUYV, UYVY, Gray, or Y16, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
@@ -226,6 +226,14 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
} else {
cur = ConvertYUYVToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kUYVY) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
cur = newImage;
} else {
cur = ConvertUYVYToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kGray) {
// Check to see if BGR version already exists...
if (Image* newImage =
@@ -234,18 +242,35 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
} else {
cur = ConvertGrayToBGR(cur);
}
}
return ConvertBGRToRGB565(cur);
case VideoMode::kGray:
// If source is YUYV or RGB565, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
} else if (cur->pixelFormat == VideoMode::kY16) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
cur = newImage;
} else if (Image* newImage = GetExistingImage(cur->width, cur->height,
VideoMode::kGray)) {
cur = ConvertGrayToBGR(newImage);
} else {
cur = ConvertYUYVToBGR(cur);
cur = ConvertGrayToBGR(ConvertY16ToGray(cur));
}
}
return ConvertBGRToRGB565(cur);
case VideoMode::kGray:
case VideoMode::kY16:
// If source is also grayscale, convert directly
if (pixelFormat == VideoMode::kGray &&
cur->pixelFormat == VideoMode::kY16) {
return ConvertY16ToGray(cur);
} else if (pixelFormat == VideoMode::kY16 &&
cur->pixelFormat == VideoMode::kGray) {
return ConvertGrayToY16(cur);
}
// If source is YUYV, UYVY, convert directly to Gray
// If RGB565, need to convert to BGR first
if (cur->pixelFormat == VideoMode::kYUYV) {
cur = ConvertYUYVToGray(cur);
} else if (cur->pixelFormat == VideoMode::kUYVY) {
cur = ConvertUYVYToGray(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
// Check to see if BGR version already exists...
if (Image* newImage =
@@ -254,12 +279,18 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
} else {
cur = ConvertRGB565ToBGR(cur);
}
cur = ConvertBGRToGray(cur);
}
return ConvertBGRToGray(cur);
if (pixelFormat == VideoMode::kY16) {
cur = ConvertGrayToY16(cur);
}
return cur;
case VideoMode::kBGR:
case VideoMode::kMJPEG:
if (cur->pixelFormat == VideoMode::kYUYV) {
cur = ConvertYUYVToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kUYVY) {
cur = ConvertUYVYToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kRGB565) {
cur = ConvertRGB565ToBGR(cur);
} else if (cur->pixelFormat == VideoMode::kGray) {
@@ -268,9 +299,23 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
} else {
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
}
} else if (cur->pixelFormat == VideoMode::kY16) {
// Check to see if Gray version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kGray)) {
cur = newImage;
} else {
cur = ConvertY16ToGray(cur);
}
if (pixelFormat == VideoMode::kBGR) {
return ConvertGrayToBGR(cur);
} else {
return ConvertGrayToMJPEG(cur, defaultJpegQuality);
}
}
break;
case VideoMode::kYUYV:
case VideoMode::kUYVY:
default:
return nullptr; // Unsupported
}
@@ -351,6 +396,72 @@ Image* Frame::ConvertYUYVToBGR(Image* image) {
return rv;
}
Image* Frame::ConvertYUYVToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kYUYV) {
return nullptr;
}
// Allocate a grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_YUYV);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::scoped_lock lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertUYVYToBGR(Image* image) {
if (!image || image->pixelFormat != VideoMode::kUYVY) {
return nullptr;
}
// Allocate a BGR image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGR, image->width, image->height,
image->width * image->height * 3);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2BGR_UYVY);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::scoped_lock lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertUYVYToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kUYVY) {
return nullptr;
}
// Allocate a grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_YUV2GRAY_UYVY);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::scoped_lock lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertBGRToRGB565(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
return nullptr;
@@ -509,6 +620,50 @@ Image* Frame::ConvertGrayToMJPEG(Image* image, int quality) {
return rv;
}
Image* Frame::ConvertGrayToY16(Image* image) {
if (!image || image->pixelFormat != VideoMode::kGray) {
return nullptr;
}
// Allocate a Y16 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kY16, image->width, image->height,
image->width * image->height * 2);
// Convert with linear scaling
image->AsMat().convertTo(newImage->AsMat(), CV_16U, 256);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::scoped_lock lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::ConvertY16ToGray(Image* image) {
if (!image || image->pixelFormat != VideoMode::kY16) {
return nullptr;
}
// Allocate a Grayscale image
auto newImage =
m_impl->source.AllocImage(VideoMode::kGray, image->width, image->height,
image->width * image->height);
// Scale min to 0 and max to 255
cv::normalize(image->AsMat(), newImage->AsMat(), 255, 0, cv::NORM_MINMAX);
// Save the result
Image* rv = newImage.release();
if (m_impl) {
std::scoped_lock lock(m_impl->mutex);
m_impl->images.push_back(rv);
}
return rv;
}
Image* Frame::GetImageImpl(int width, int height,
VideoMode::PixelFormat pixelFormat,
int requiredJpegQuality, int defaultJpegQuality) {

View File

@@ -195,12 +195,17 @@ class Frame {
Image* ConvertMJPEGToBGR(Image* image);
Image* ConvertMJPEGToGray(Image* image);
Image* ConvertYUYVToBGR(Image* image);
Image* ConvertYUYVToGray(Image* image);
Image* ConvertUYVYToBGR(Image* image);
Image* ConvertUYVYToGray(Image* image);
Image* ConvertBGRToRGB565(Image* image);
Image* ConvertRGB565ToBGR(Image* image);
Image* ConvertBGRToGray(Image* image);
Image* ConvertGrayToBGR(Image* image);
Image* ConvertBGRToMJPEG(Image* image, int quality);
Image* ConvertGrayToMJPEG(Image* image, int quality);
Image* ConvertGrayToY16(Image* image);
Image* ConvertY16ToGray(Image* image);
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat) {
if (pixelFormat == VideoMode::kMJPEG) {

View File

@@ -75,7 +75,7 @@ void HttpCameraImpl::MonitorThreadMain() {
std::unique_lock lock(m_mutex);
// sleep for 1 second between checks
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
[=] { return !m_active; });
[=, this] { return !m_active; });
if (!m_active) {
break;
@@ -85,7 +85,7 @@ void HttpCameraImpl::MonitorThreadMain() {
// (this will result in an error at the read point, and ultimately
// a reconnect attempt)
if (m_streamConn && m_frameCount == 0) {
SWARNING("{}", "Monitor detected stream hung, disconnecting");
SWARNING("Monitor detected stream hung, disconnecting");
m_streamConn->stream->close();
}
@@ -93,7 +93,7 @@ void HttpCameraImpl::MonitorThreadMain() {
m_frameCount = 0;
}
SDEBUG("{}", "Monitor Thread exiting");
SDEBUG("Monitor Thread exiting");
}
void HttpCameraImpl::StreamThreadMain() {
@@ -110,7 +110,8 @@ void HttpCameraImpl::StreamThreadMain() {
m_streamConn->stream->close();
}
// Wait for enable
m_sinkEnabledCond.wait(lock, [=] { return !m_active || IsEnabled(); });
m_sinkEnabledCond.wait(lock,
[=, this] { return !m_active || IsEnabled(); });
if (!m_active) {
return;
}
@@ -140,7 +141,7 @@ void HttpCameraImpl::StreamThreadMain() {
}
}
SDEBUG("{}", "Camera Thread exiting");
SDEBUG("Camera Thread exiting");
SetConnected(false);
}
@@ -151,7 +152,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
{
std::scoped_lock lock(m_mutex);
if (m_locations.empty()) {
SERROR("{}", "locations array is empty!?");
SERROR("locations array is empty!?");
std::this_thread::sleep_for(std::chrono::seconds(1));
return nullptr;
}
@@ -272,7 +273,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
wpi::SmallString<64> contentTypeBuf;
wpi::SmallString<64> contentLengthBuf;
if (!ParseHttpHeaders(is, &contentTypeBuf, &contentLengthBuf)) {
SWARNING("{}", "disconnected during headers");
SWARNING("disconnected during headers");
PutError("disconnected during headers", wpi::Now());
return false;
}
@@ -294,7 +295,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
// Ugh, no Content-Length? Read the blocks of the JPEG file.
int width, height;
if (!ReadJpeg(is, imageBuf, &width, &height)) {
SWARNING("{}", "did not receive a JPEG image");
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
@@ -313,7 +314,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
}
int width, height;
if (!GetJpegSize(image->str(), &width, &height)) {
SWARNING("{}", "did not receive a JPEG image");
SWARNING("did not receive a JPEG image");
PutError("did not receive a JPEG image", wpi::Now());
return false;
}
@@ -329,7 +330,7 @@ void HttpCameraImpl::SettingsThreadMain() {
wpi::HttpRequest req;
{
std::unique_lock lock(m_mutex);
m_settingsCond.wait(lock, [=] {
m_settingsCond.wait(lock, [=, this] {
return !m_active || (m_prefLocation != -1 && !m_settings.empty());
});
if (!m_active) {
@@ -343,7 +344,7 @@ void HttpCameraImpl::SettingsThreadMain() {
DeviceSendSettings(req);
}
SDEBUG("{}", "Settings Thread exiting");
SDEBUG("Settings Thread exiting");
}
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
@@ -378,7 +379,7 @@ CS_HttpCameraKind HttpCameraImpl::GetKind() const {
return m_kind;
}
bool HttpCameraImpl::SetUrls(wpi::span<const std::string> urls,
bool HttpCameraImpl::SetUrls(std::span<const std::string> urls,
CS_Status* status) {
std::vector<wpi::HttpLocation> locations;
for (const auto& url : urls) {
@@ -572,14 +573,14 @@ CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
break;
}
std::string urlStr{url};
if (!source->SetUrls(wpi::span{&urlStr, 1}, status)) {
if (!source->SetUrls(std::span{&urlStr, 1}, status)) {
return 0;
}
return inst.CreateSource(CS_SOURCE_HTTP, source);
}
CS_Source CreateHttpCamera(std::string_view name,
wpi::span<const std::string> urls,
std::span<const std::string> urls,
CS_HttpCameraKind kind, CS_Status* status) {
auto& inst = Instance::GetInstance();
if (urls.empty()) {
@@ -603,7 +604,7 @@ CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
return static_cast<HttpCameraImpl&>(*data->source).GetKind();
}
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
CS_Status* status) {
if (urls.empty()) {
*status = CS_EMPTY_VALUE;

View File

@@ -9,6 +9,7 @@
#include <functional>
#include <initializer_list>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <thread>
@@ -18,7 +19,6 @@
#include <wpi/StringMap.h>
#include <wpi/condition_variable.h>
#include <wpi/raw_istream.h>
#include <wpi/span.h>
#include <wpinet/HttpUtil.h>
#include "SourceImpl.h"
@@ -55,7 +55,7 @@ class HttpCameraImpl : public SourceImpl {
void NumSinksEnabledChanged() override;
CS_HttpCameraKind GetKind() const;
bool SetUrls(wpi::span<const std::string> urls, CS_Status* status);
bool SetUrls(std::span<const std::string> urls, CS_Status* status);
std::vector<std::string> GetUrls() const;
// Property data

View File

@@ -74,6 +74,8 @@ class Image {
switch (pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
case VideoMode::kY16:
case VideoMode::kUYVY:
type = CV_8UC2;
break;
case VideoMode::kBGR:

View File

@@ -86,16 +86,16 @@ class Instance {
void DestroySource(CS_Source handle);
void DestroySink(CS_Sink handle);
wpi::span<CS_Source> EnumerateSourceHandles(
std::span<CS_Source> EnumerateSourceHandles(
wpi::SmallVectorImpl<CS_Source>& vec) {
return m_sources.GetAll(vec);
}
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec) {
return m_sinks.GetAll(vec);
}
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
wpi::SmallVectorImpl<CS_Sink>& vec) {
vec.clear();
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {

View File

@@ -27,26 +27,37 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
} // namespace cs
#define LOG(level, format, ...) WPI_LOG(m_logger, level, format, __VA_ARGS__)
#define LOG(level, format, ...) \
WPI_LOG(m_logger, level, format __VA_OPT__(, ) __VA_ARGS__)
#undef ERROR
#define ERROR(format, ...) WPI_ERROR(m_logger, format, __VA_ARGS__)
#define WARNING(format, ...) WPI_WARNING(m_logger, format, __VA_ARGS__)
#define INFO(format, ...) WPI_INFO(m_logger, format, __VA_ARGS__)
#define ERROR(format, ...) \
WPI_ERROR(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define WARNING(format, ...) \
WPI_WARNING(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define INFO(format, ...) WPI_INFO(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define DEBUG0(format, ...) WPI_DEBUG(m_logger, format, __VA_ARGS__)
#define DEBUG1(format, ...) WPI_DEBUG1(m_logger, format, __VA_ARGS__)
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
#define DEBUG0(format, ...) \
WPI_DEBUG(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define DEBUG1(format, ...) \
WPI_DEBUG1(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define DEBUG2(format, ...) \
WPI_DEBUG2(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define DEBUG3(format, ...) \
WPI_DEBUG3(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define DEBUG4(format, ...) \
WPI_DEBUG4(m_logger, format __VA_OPT__(, ) __VA_ARGS__)
#define SLOG(level, format, ...) \
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), FMT_STRING(format), \
__VA_ARGS__)
#define SLOG(level, format, ...) \
NamedLog(m_logger, level, __FILE__, __LINE__, GetName(), \
FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__)
#define SERROR(format, ...) SLOG(::wpi::WPI_LOG_ERROR, format, __VA_ARGS__)
#define SWARNING(format, ...) SLOG(::wpi::WPI_LOG_WARNING, format, __VA_ARGS__)
#define SINFO(format, ...) SLOG(::wpi::WPI_LOG_INFO, format, __VA_ARGS__)
#define SERROR(format, ...) \
SLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__)
#define SWARNING(format, ...) \
SLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__)
#define SINFO(format, ...) \
SLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__)
#ifdef NDEBUG
#define SDEBUG(format, ...) \
@@ -65,11 +76,16 @@ inline void NamedLog(wpi::Logger& logger, unsigned int level, const char* file,
do { \
} while (0)
#else
#define SDEBUG(format, ...) SLOG(::wpi::WPI_LOG_DEBUG, format, __VA_ARGS__)
#define SDEBUG1(format, ...) SLOG(::wpi::WPI_LOG_DEBUG1, format, __VA_ARGS__)
#define SDEBUG2(format, ...) SLOG(::wpi::WPI_LOG_DEBUG2, format, __VA_ARGS__)
#define SDEBUG3(format, ...) SLOG(::wpi::WPI_LOG_DEBUG3, format, __VA_ARGS__)
#define SDEBUG4(format, ...) SLOG(::wpi::WPI_LOG_DEBUG4, format, __VA_ARGS__)
#define SDEBUG(format, ...) \
SLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__)
#define SDEBUG1(format, ...) \
SLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__)
#define SDEBUG2(format, ...) \
SLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__)
#define SDEBUG3(format, ...) \
SLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__)
#define SDEBUG4(format, ...) \
SLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__)
#endif
#endif // CSCORE_LOG_H_

View File

@@ -460,6 +460,12 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
case VideoMode::kGray:
os << "gray";
break;
case VideoMode::kY16:
os << "Y16";
break;
case VideoMode::kUYVY:
os << "UYVY";
break;
default:
os << "unknown";
break;
@@ -569,6 +575,12 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
case VideoMode::kGray:
os << "gray";
break;
case VideoMode::kY16:
os << "Y16";
break;
case VideoMode::kUYVY:
os << "UYVY";
break;
default:
os << "unknown";
break;
@@ -650,7 +662,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 +675,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 +697,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;
@@ -740,8 +752,10 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
// for adding it if required.
addDHT = JpegNeedsDHT(data, &size, &locSOF);
break;
case VideoMode::kYUYV:
case VideoMode::kUYVY:
case VideoMode::kRGB565:
case VideoMode::kYUYV:
case VideoMode::kY16:
default:
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
@@ -783,7 +797,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 +838,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 +880,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 +892,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 +903,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 +914,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 +941,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 +991,7 @@ void MjpegServerImpl::ServerThreadMain() {
thr->m_cond.notify_one();
}
SDEBUG("{}", "leaving server thread");
SDEBUG("leaving server thread");
}
void MjpegServerImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {

View File

@@ -27,7 +27,7 @@ int PropertyContainer::GetPropertyIndex(std::string_view name) const {
return ndx;
}
wpi::span<int> PropertyContainer::EnumerateProperties(
std::span<int> PropertyContainer::EnumerateProperties(
wpi::SmallVectorImpl<int>& vec, CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status)) {
return {};

View File

@@ -8,13 +8,13 @@
#include <atomic>
#include <cstddef>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/StringMap.h>
#include <wpi/mutex.h>
#include <wpi/span.h>
#include "PropertyImpl.h"
#include "cscore_cpp.h"
@@ -33,7 +33,7 @@ class PropertyContainer {
virtual ~PropertyContainer() = default;
int GetPropertyIndex(std::string_view name) const;
wpi::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
std::span<int> EnumerateProperties(wpi::SmallVectorImpl<int>& vec,
CS_Status* status) const;
CS_PropertyKind GetPropertyKind(int property) const;
std::string_view GetPropertyName(int property,

View File

@@ -127,7 +127,7 @@ void RawSinkImpl::ThreadMain() {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
SDEBUG4("{}", "waiting for frame");
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(); // blocks
if (!m_active) {
break;

View File

@@ -26,6 +26,8 @@ void RawSourceImpl::PutFrame(const CS_RawFrame& image) {
switch (image.pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
case VideoMode::kY16:
case VideoMode::kUYVY:
type = CV_8UC2;
break;
case VideoMode::kBGR:

View File

@@ -76,7 +76,7 @@ Frame SourceImpl::GetCurFrame() {
Frame SourceImpl::GetNextFrame() {
std::unique_lock lock{m_frameMutex};
auto oldTime = m_frame.GetTime();
m_frameCv.wait(lock, [=] { return m_frame.GetTime() != oldTime; });
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
return m_frame;
}
@@ -85,7 +85,7 @@ Frame SourceImpl::GetNextFrame(double timeout) {
auto oldTime = m_frame.GetTime();
if (!m_frameCv.wait_for(
lock, std::chrono::milliseconds(static_cast<int>(timeout * 1000)),
[=] { return m_frame.GetTime() != oldTime; })) {
[=, this] { return m_frame.GetTime() != oldTime; })) {
m_frame = Frame{*this, "timed out getting frame", wpi::Now()};
}
return m_frame;
@@ -198,6 +198,10 @@ bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
mode.pixelFormat = cs::VideoMode::kBGR;
} else if (wpi::equals_lower(str, "gray")) {
mode.pixelFormat = cs::VideoMode::kGray;
} else if (wpi::equals_lower(str, "y16")) {
mode.pixelFormat = cs::VideoMode::kY16;
} else if (wpi::equals_lower(str, "uyvy")) {
mode.pixelFormat = cs::VideoMode::kUYVY;
} else {
SWARNING("SetConfigJson: could not understand pixel format value '{}'",
str);
@@ -360,6 +364,12 @@ wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
case VideoMode::kGray:
pixelFormat = "gray";
break;
case VideoMode::kY16:
pixelFormat = "y16";
break;
case VideoMode::kUYVY:
pixelFormat = "uyvy";
break;
default:
break;
}

View File

@@ -6,12 +6,12 @@
#define CSCORE_UNLIMITEDHANDLERESOURCE_H_
#include <memory>
#include <span>
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/mutex.h>
#include <wpi/span.h>
namespace cs {
@@ -50,7 +50,7 @@ class UnlimitedHandleResource {
std::shared_ptr<TStruct> Free(THandle handle);
template <typename T>
wpi::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
std::span<T> GetAll(wpi::SmallVectorImpl<T>& vec);
std::vector<std::shared_ptr<TStruct>> FreeAll();
@@ -151,7 +151,7 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename T>
inline wpi::span<T>
inline std::span<T>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
wpi::SmallVectorImpl<T>& vec) {
ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); });

View File

@@ -286,7 +286,7 @@ CS_Property GetSourceProperty(CS_Source source, std::string_view name,
return Handle{source, property, Handle::kProperty};
}
wpi::span<CS_Property> EnumerateSourceProperties(
std::span<CS_Property> EnumerateSourceProperties(
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
@@ -398,7 +398,7 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
return data->source->EnumerateVideoModes(status);
}
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status) {
auto& inst = Instance::GetInstance();
@@ -583,7 +583,7 @@ CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
return Handle{sink, property, Handle::kSinkProperty};
}
wpi::span<CS_Property> EnumerateSinkProperties(
std::span<CS_Property> EnumerateSinkProperties(
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
@@ -865,12 +865,12 @@ void Shutdown() {
// Utility Functions
//
wpi::span<CS_Source> EnumerateSourceHandles(
std::span<CS_Source> EnumerateSourceHandles(
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
return Instance::GetInstance().EnumerateSourceHandles(vec);
}
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status) {
return Instance::GetInstance().EnumerateSinkHandles(vec);
}

View File

@@ -3,12 +3,12 @@
// the WPILib BSD license file in the root directory of this project.
#include <exception>
#include <span>
#include <fmt/format.h>
#include <opencv2/core/core.hpp>
#include <wpi/SmallString.h>
#include <wpi/jni_util.h>
#include <wpi/span.h>
#include "cscore_cpp.h"
#include "cscore_cv.h"
@@ -296,7 +296,7 @@ static jobject MakeJObject(JNIEnv* env, const cs::RawEvent& event) {
}
static jobjectArray MakeJObject(JNIEnv* env,
wpi::span<const cs::RawEvent> arr) {
std::span<const cs::RawEvent> arr) {
jobjectArray jarr = env->NewObjectArray(arr.size(), videoEventCls, nullptr);
if (!jarr) {
return nullptr;

View File

@@ -93,7 +93,9 @@ enum CS_PixelFormat {
CS_PIXFMT_YUYV,
CS_PIXFMT_RGB565,
CS_PIXFMT_BGR,
CS_PIXFMT_GRAY
CS_PIXFMT_GRAY,
CS_PIXFMT_Y16,
CS_PIXFMT_UYVY
};
/**

View File

@@ -8,12 +8,12 @@
#include <stdint.h>
#include <functional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/span.h>
#include "cscore_c.h"
@@ -68,7 +68,9 @@ struct VideoMode : public CS_VideoMode {
kYUYV = CS_PIXFMT_YUYV,
kRGB565 = CS_PIXFMT_RGB565,
kBGR = CS_PIXFMT_BGR,
kGray = CS_PIXFMT_GRAY
kGray = CS_PIXFMT_GRAY,
kY16 = CS_PIXFMT_Y16,
kUYVY = CS_PIXFMT_UYVY
};
VideoMode() {
pixelFormat = 0;
@@ -88,8 +90,6 @@ struct VideoMode : public CS_VideoMode {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height && fps == other.fps;
}
bool operator!=(const VideoMode& other) const { return !(*this == other); }
};
/**
@@ -203,7 +203,7 @@ CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
CS_Source CreateHttpCamera(std::string_view name, std::string_view url,
CS_HttpCameraKind kind, CS_Status* status);
CS_Source CreateHttpCamera(std::string_view name,
wpi::span<const std::string> urls,
std::span<const std::string> urls,
CS_HttpCameraKind kind, CS_Status* status);
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
CS_Status* status);
@@ -230,7 +230,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status);
bool IsSourceEnabled(CS_Source source, CS_Status* status);
CS_Property GetSourceProperty(CS_Source source, std::string_view name,
CS_Status* status);
wpi::span<CS_Property> EnumerateSourceProperties(
std::span<CS_Property> EnumerateSourceProperties(
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
CS_Status* status);
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status);
@@ -249,7 +249,7 @@ std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status);
wpi::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
std::span<CS_Sink> EnumerateSourceSinks(CS_Source source,
wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status);
CS_Source CopySource(CS_Source source, CS_Status* status);
@@ -285,7 +285,7 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
* @{
*/
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status);
void SetHttpCameraUrls(CS_Source source, wpi::span<const std::string> urls,
void SetHttpCameraUrls(CS_Source source, std::span<const std::string> urls,
CS_Status* status);
std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
/** @} */
@@ -304,7 +304,7 @@ CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
int step, int defaultValue, int value,
CS_Status* status);
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
wpi::span<const std::string> choices,
std::span<const std::string> choices,
CS_Status* status);
/** @} */
@@ -335,7 +335,7 @@ std::string_view GetSinkDescription(CS_Sink sink,
CS_Status* status);
CS_Property GetSinkProperty(CS_Sink sink, std::string_view name,
CS_Status* status);
wpi::span<CS_Property> EnumerateSinkProperties(
std::span<CS_Property> EnumerateSinkProperties(
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status);
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property GetSinkSourceProperty(CS_Sink sink, std::string_view name,
@@ -430,9 +430,9 @@ void Shutdown();
*/
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status);
wpi::span<CS_Source> EnumerateSourceHandles(
std::span<CS_Source> EnumerateSourceHandles(
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status);
wpi::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
std::span<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status);
std::string GetHostname();

View File

@@ -6,13 +6,12 @@
#define CSCORE_CSCORE_OO_H_
#include <initializer_list>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/span.h>
#include "cscore_cpp.h"
namespace cs {
@@ -141,8 +140,6 @@ class VideoSource {
return m_handle == other.m_handle;
}
bool operator!=(const VideoSource& other) const { return !(*this == other); }
/**
* Get the kind of the source.
*/
@@ -516,7 +513,7 @@ class HttpCamera : public VideoCamera {
* @param urls Array of Camera URLs
* @param kind Camera kind (e.g. kAxis)
*/
HttpCamera(std::string_view name, wpi::span<const std::string> urls,
HttpCamera(std::string_view name, std::span<const std::string> urls,
HttpCameraKind kind = kUnknown);
/**
@@ -541,7 +538,7 @@ class HttpCamera : public VideoCamera {
/**
* Change the URLs used to connect to the camera.
*/
void SetUrls(wpi::span<const std::string> urls);
void SetUrls(std::span<const std::string> urls);
/**
* Change the URLs used to connect to the camera.
@@ -560,7 +557,7 @@ class HttpCamera : public VideoCamera {
*/
class AxisCamera : public HttpCamera {
static std::string HostToUrl(std::string_view host);
static std::vector<std::string> HostToUrl(wpi::span<const std::string> hosts);
static std::vector<std::string> HostToUrl(std::span<const std::string> hosts);
template <typename T>
static std::vector<std::string> HostToUrl(std::initializer_list<T> hosts);
@@ -595,7 +592,7 @@ class AxisCamera : public HttpCamera {
* @param name Source name (arbitrary unique identifier)
* @param hosts Array of Camera host IPs/DNS names
*/
AxisCamera(std::string_view name, wpi::span<const std::string> hosts);
AxisCamera(std::string_view name, std::span<const std::string> hosts);
/**
* Create a source for an Axis IP camera.
@@ -696,7 +693,7 @@ class ImageSource : public VideoSource {
* @param choices Choices
*/
void SetEnumPropertyChoices(const VideoProperty& property,
wpi::span<const std::string> choices);
std::span<const std::string> choices);
/**
* Configure enum property choices.
@@ -737,8 +734,6 @@ class VideoSink {
return m_handle == other.m_handle;
}
bool operator!=(const VideoSink& other) const { return !(*this == other); }
/**
* Get the kind of the sink.
*/

View File

@@ -302,7 +302,7 @@ inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
: HttpCamera(name, std::string_view{url}, kind) {}
inline HttpCamera::HttpCamera(std::string_view name,
wpi::span<const std::string> urls,
std::span<const std::string> urls,
HttpCameraKind kind) {
m_handle = CreateHttpCamera(
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
@@ -329,7 +329,7 @@ inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
}
inline void HttpCamera::SetUrls(wpi::span<const std::string> urls) {
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
m_status = 0;
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
}
@@ -351,7 +351,7 @@ inline std::vector<std::string> HttpCamera::GetUrls() const {
}
inline std::vector<std::string> AxisCamera::HostToUrl(
wpi::span<const std::string> hosts) {
std::span<const std::string> hosts) {
std::vector<std::string> rv;
rv.reserve(hosts.size());
for (const auto& host : hosts) {
@@ -381,7 +381,7 @@ inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
inline AxisCamera::AxisCamera(std::string_view name,
wpi::span<const std::string> hosts)
std::span<const std::string> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
template <typename T>
@@ -452,7 +452,7 @@ inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
}
inline void ImageSource::SetEnumPropertyChoices(
const VideoProperty& property, wpi::span<const std::string> choices) {
const VideoProperty& property, std::span<const std::string> choices) {
m_status = 0;
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
}

View File

@@ -82,6 +82,10 @@ static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) {
return VideoMode::kBGR;
case V4L2_PIX_FMT_GREY:
return VideoMode::kGray;
case V4L2_PIX_FMT_Y16:
return VideoMode::kY16;
case V4L2_PIX_FMT_UYVY:
return VideoMode::kUYVY;
default:
return VideoMode::kUnknown;
}
@@ -100,6 +104,10 @@ static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
return V4L2_PIX_FMT_BGR24;
case VideoMode::kGray:
return V4L2_PIX_FMT_GREY;
case VideoMode::kY16:
return V4L2_PIX_FMT_Y16;
case VideoMode::kUYVY:
return V4L2_PIX_FMT_UYVY;
default:
return 0;
}
@@ -140,6 +148,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 +173,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 +474,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 +503,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 +513,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 +521,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 +545,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 +556,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 +599,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 +607,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 +619,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 +645,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 +709,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 +728,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 +737,7 @@ bool UsbCameraImpl::DeviceStreamOn() {
}
return false;
}
SDEBUG4("{}", "enabled streaming");
SDEBUG4("enabled streaming");
m_streaming = true;
return true;
}
@@ -735,7 +754,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 +1019,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 +1404,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 +1688,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

@@ -2,6 +2,7 @@
// 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 "Instance.h"
#include "cscore_cpp.h"
namespace cs {
@@ -9,12 +10,16 @@ namespace cs {
CS_Source CreateUsbCameraDev(std::string_view name, int dev,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
WPI_ERROR(Instance::GetInstance().logger,
"USB Camera support not implemented for macOS");
return 0;
}
CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
WPI_ERROR(Instance::GetInstance().logger,
"USB Camera support not implemented for macOS");
return 0;
}
@@ -35,6 +40,8 @@ UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
*status = CS_INVALID_HANDLE;
WPI_ERROR(Instance::GetInstance().logger,
"USB Camera support not implemented for macOS");
return std::vector<UsbCameraInfo>{};
}

View File

@@ -356,6 +356,12 @@ void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
tmpMat.total());
tmpMat.copyTo(dest->AsMat());
break;
case cs::VideoMode::PixelFormat::kY16:
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC2, ptr, pitch);
dest =
AllocImage(VideoMode::kY16, tmpMat.cols, tmpMat.rows, tmpMat.total());
tmpMat.copyTo(dest->AsMat());
break;
case cs::VideoMode::PixelFormat::kBGR:
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC3, ptr, pitch);
dest = AllocImage(VideoMode::kBGR, tmpMat.cols, tmpMat.rows,
@@ -368,6 +374,12 @@ void UsbCameraImpl::ProcessFrame(IMFSample* videoSample,
tmpMat.total() * 2);
tmpMat.copyTo(dest->AsMat());
break;
case cs::VideoMode::PixelFormat::kUYVY:
tmpMat = cv::Mat(mode.height, mode.width, CV_8UC2, ptr, pitch);
dest = AllocImage(VideoMode::kUYVY, tmpMat.cols, tmpMat.rows,
tmpMat.total() * 2);
tmpMat.copyTo(dest->AsMat());
break;
default:
doFinalSet = false;
break;
@@ -461,9 +473,10 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
// Compare GUID to one of the supported ones
if (IsEqualGUID(guid, MFVideoFormat_NV12)) {
// GrayScale
if (IsEqualGUID(guid, MFVideoFormat_L8)) {
return cs::VideoMode::PixelFormat::kGray;
} else if (IsEqualGUID(guid, MFVideoFormat_L16)) {
return cs::VideoMode::PixelFormat::kY16;
} else if (IsEqualGUID(guid, MFVideoFormat_YUY2)) {
return cs::VideoMode::PixelFormat::kYUYV;
} else if (IsEqualGUID(guid, MFVideoFormat_RGB24)) {
@@ -472,6 +485,8 @@ static cs::VideoMode::PixelFormat GetFromGUID(const GUID& guid) {
return cs::VideoMode::PixelFormat::kMJPEG;
} else if (IsEqualGUID(guid, MFVideoFormat_RGB565)) {
return cs::VideoMode::PixelFormat::kRGB565;
} else if (IsEqualGUID(guid, MFVideoFormat_UYVY)) {
return cs::VideoMode::PixelFormat::kUYVY;
} else {
return cs::VideoMode::PixelFormat::kUnknown;
}
@@ -486,7 +501,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 +535,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,8 @@ 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"
apply from: "${rootDir}/shared/imgui.gradle"
task generateCppVersion() {
description = 'Generates the wpilib version class'
@@ -107,7 +96,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
}
@@ -115,7 +104,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
lib project: ':glass', library: 'glass', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static', 'libssh')
nativeUtils.useRequiredLibrary(it, 'imgui', 'libssh')
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
it.linker.args << 'ws2_32.lib' << 'advapi32.lib' << 'crypt32.lib' << 'user32.lib'
@@ -124,6 +113,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

@@ -20,7 +20,7 @@
class DataLogThread {
public:
explicit DataLogThread(wpi::log::DataLogReader reader)
: m_reader{std::move(reader)}, m_thread{[=] { ReadMain(); }} {}
: m_reader{std::move(reader)}, m_thread{[=, this] { ReadMain(); }} {}
~DataLogThread();
bool IsDone() const { return m_done; }

View File

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

View File

@@ -107,7 +107,7 @@ static void RebuildEntryTree() {
// get to leaf
auto nodes = &gEntryTree;
for (auto part : wpi::drop_back(wpi::span{parts.begin(), parts.end()})) {
for (auto part : wpi::drop_back(std::span{parts.begin(), parts.end()})) {
auto it =
std::find_if(nodes->begin(), nodes->end(),
[&](const auto& node) { return node.name == part; });

View File

@@ -90,7 +90,7 @@ size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
return rv;
}
size_t File::Write(wpi::span<const uint8_t> data) {
size_t File::Write(std::span<const uint8_t> data) {
auto rv = sftp_write(m_handle, data.data(), data.size());
if (rv < 0) {
throw Exception{m_handle->sftp};

View File

@@ -7,13 +7,12 @@
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/span.h>
namespace sftp {
struct Attributes {
@@ -53,7 +52,7 @@ class File {
size_t Read(void* buf, uint32_t count);
AsyncId AsyncReadBegin(uint32_t len) const;
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
size_t Write(wpi::span<const uint8_t> data);
size_t Write(std::span<const uint8_t> data);
void Seek(uint64_t offset);
uint64_t Tell() const;

View File

@@ -3,15 +3,17 @@ plugins {
id "org.ysb33r.doxygen" version "0.7.0"
}
evaluationDependsOn(':wpiutil')
evaluationDependsOn(':ntcore')
evaluationDependsOn(':apriltag')
evaluationDependsOn(':cameraserver')
evaluationDependsOn(':cscore')
evaluationDependsOn(':hal')
evaluationDependsOn(':cameraserver')
evaluationDependsOn(':wpimath')
evaluationDependsOn(':ntcore')
evaluationDependsOn(':wpilibNewCommands')
evaluationDependsOn(':wpilibc')
evaluationDependsOn(':wpilibj')
evaluationDependsOn(':wpilibNewCommands')
evaluationDependsOn(':wpimath')
evaluationDependsOn(':wpinet')
evaluationDependsOn(':wpiutil')
def baseArtifactIdCpp = 'documentation'
def artifactGroupIdCpp = 'edu.wpi.first.wpilibc'
@@ -26,14 +28,16 @@ def outputsFolder = file("$project.buildDir/outputs")
def cppProjectZips = []
def cppIncludeRoots = []
cppProjectZips.add(project(':hal').cppHeadersZip)
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
cppProjectZips.add(project(':ntcore').cppHeadersZip)
cppProjectZips.add(project(':cscore').cppHeadersZip)
cppProjectZips.add(project(':apriltag').cppHeadersZip)
cppProjectZips.add(project(':cameraserver').cppHeadersZip)
cppProjectZips.add(project(':wpimath').cppHeadersZip)
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
cppProjectZips.add(project(':cscore').cppHeadersZip)
cppProjectZips.add(project(':hal').cppHeadersZip)
cppProjectZips.add(project(':ntcore').cppHeadersZip)
cppProjectZips.add(project(':wpilibNewCommands').cppHeadersZip)
cppProjectZips.add(project(':wpilibc').cppHeadersZip)
cppProjectZips.add(project(':wpimath').cppHeadersZip)
cppProjectZips.add(project(':wpinet').cppHeadersZip)
cppProjectZips.add(project(':wpiutil').cppHeadersZip)
doxygen {
executables {
@@ -57,7 +61,6 @@ doxygen {
if (project.hasProperty('docWarningsAsErrors')) {
// C++20 shims
exclude 'wpi/ghc/filesystem.hpp'
exclude 'wpi/span.h'
// Drake
exclude 'drake/common/**'
@@ -110,7 +113,7 @@ doxygen {
// libuv
exclude 'uv.h'
exclude 'uv/**'
exclude 'wpi/uv/**'
exclude 'wpinet/uv/**'
// json
exclude 'wpi/json.h'
@@ -125,6 +128,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
@@ -151,7 +163,7 @@ doxygen {
warn_if_undocumented false
warn_no_paramdoc true
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix SpeedController docs
//enable doxygen preprocessor expansion of WPI_DEPRECATED to fix MotorController docs
enable_preprocessing true
macro_expansion true
expand_only_predef true
@@ -195,17 +207,20 @@ task generateJavaDocs(type: Javadoc) {
options.addBooleanOption("Xdoclint:html,missing,reference,syntax", true)
options.addBooleanOption('html5', true)
options.linkSource(true)
dependsOn project(':wpilibj').generateJavaVersion
dependsOn project(':hal').generateUsageReporting
dependsOn project(':ntcore').ntcoreGenerateJavaTypes
dependsOn project(':wpilibj').generateJavaVersion
dependsOn project(':wpimath').generateNat
source project(':hal').sourceSets.main.java
source project(':wpiutil').sourceSets.main.java
source project(':cscore').sourceSets.main.java
source project(':ntcore').sourceSets.main.java
source project(':wpimath').sourceSets.main.java
source project(':wpilibj').sourceSets.main.java
source project(':apriltag').sourceSets.main.java
source project(':cameraserver').sourceSets.main.java
source project(':cscore').sourceSets.main.java
source project(':hal').sourceSets.main.java
source project(':ntcore').sourceSets.main.java
source project(':wpilibNewCommands').sourceSets.main.java
source project(':wpilibj').sourceSets.main.java
source project(':wpimath').sourceSets.main.java
source project(':wpinet').sourceSets.main.java
source project(':wpiutil').sourceSets.main.java
source configurations.javaSource.collect { zipTree(it) }
include '**/*.java'
failOnError = true

View File

@@ -29,7 +29,7 @@ add_library(fieldImages ${field_images_resources_src})
set_target_properties(fieldImages PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET fieldImages PROPERTY FOLDER "libraries")
target_compile_features(fieldImages PUBLIC cxx_std_17)
target_compile_features(fieldImages PUBLIC cxx_std_20)
if (MSVC)
target_compile_options(fieldImages PUBLIC /bigobj)
endif()

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxarm32') && !project.hasProperty('onlylinuxarm64')) {
if (!project.hasProperty('onlylinuxathena')) {
description = "A different kind of dashboard"
@@ -24,6 +24,8 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
def wpilibVersionFileInput = file("src/app/generate/WPILibVersion.cpp.in")
def wpilibVersionFileOutput = file("$buildDir/generated/app/cpp/WPILibVersion.cpp")
apply from: "${rootDir}/shared/imgui.gradle"
task generateCppVersion() {
description = 'Generates the wpilib version class'
group = 'WPILib'
@@ -100,7 +102,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
}
@@ -111,7 +113,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
nativeUtils.useRequiredLibrary(it, 'imgui')
}
appendDebugPathToBinaries(binaries)
}
@@ -128,7 +130,7 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
@@ -137,12 +139,12 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
return
}
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'shared'
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static')
nativeUtils.useRequiredLibrary(it, 'imgui')
}
appendDebugPathToBinaries(binaries)
}
@@ -169,20 +171,20 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxar
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm32 || it.targetPlatform.name == nativeUtils.wpi.platforms.linuxarm64) {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib library: 'glassnt', linkage: 'static'
lib library: nativeName, linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
project(':ntcore').addNtcoreDependency(it, 'static')
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpimath', library: 'wpimath', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'opencv_static')
nativeUtils.useRequiredLibrary(it, 'imgui_static')
nativeUtils.useRequiredLibrary(it, 'imgui')
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
it.linker.args << '/DELAYLOAD:MF.dll' << '/DELAYLOAD:MFReadWrite.dll' << '/DELAYLOAD:MFPlat.dll' << '/delay:nobind'
@@ -190,6 +192,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

@@ -42,6 +42,7 @@ static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
static glass::LogData gNetworkTablesLog;
static std::unique_ptr<glass::Window> gNetworkTablesWindow;
static std::unique_ptr<glass::Window> gNetworkTablesInfoWindow;
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;
@@ -69,46 +70,41 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode,
}
static void NtInitialize() {
// update window title when connection status changes
auto inst = nt::GetDefaultInstance();
auto poller = nt::CreateConnectionListenerPoller(inst);
nt::AddPolledConnectionListener(poller, true);
auto poller = nt::CreateListenerPoller(inst);
nt::AddPolledListener(
poller, inst,
NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE);
gui::AddEarlyExecute([poller] {
auto win = gui::GetSystemWindow();
if (!win) {
return;
}
bool timedOut;
for (auto&& event : nt::PollConnectionListener(poller, 0, &timedOut)) {
if (event.connected) {
glfwSetWindowTitle(
win, fmt::format("Glass - Connected ({})", event.conn.remote_ip)
.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
for (auto&& event : nt::ReadListenerQueue(poller)) {
if (auto connInfo = event.GetConnectionInfo()) {
// update window title when connection status changes
if ((event.flags & NT_EVENT_CONNECTED) != 0) {
glfwSetWindowTitle(
win, fmt::format("Glass - Connected ({})", connInfo->remote_ip)
.c_str());
} else {
glfwSetWindowTitle(win, "Glass - DISCONNECTED");
}
} else if (auto msg = event.GetLogMessage()) {
const char* level = "";
if (msg->level >= NT_LOG_CRITICAL) {
level = "CRITICAL: ";
} else if (msg->level >= NT_LOG_ERROR) {
level = "ERROR: ";
} else if (msg->level >= NT_LOG_WARNING) {
level = "WARNING: ";
}
gNetworkTablesLog.Append(fmt::format(
"{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line));
}
}
});
// handle NetworkTables log messages
auto logPoller = nt::CreateLoggerPoller(inst);
nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100);
gui::AddEarlyExecute([logPoller] {
bool timedOut;
for (auto&& msg : nt::PollLogger(logPoller, 0, &timedOut)) {
const char* level = "";
if (msg.level >= NT_LOG_CRITICAL) {
level = "CRITICAL: ";
} else if (msg.level >= NT_LOG_ERROR) {
level = "ERROR: ";
} else if (msg.level >= NT_LOG_WARNING) {
level = "WARNING: ";
}
gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message,
msg.filename, msg.line));
}
});
gNetworkTablesLogWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Log"),
"NetworkTables Log", glass::Window::kHide);
@@ -132,9 +128,21 @@ static void NtInitialize() {
gNetworkTablesWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
// NetworkTables info window
gNetworkTablesInfoWindow = std::make_unique<glass::Window>(
glass::GetStorageRoot().GetChild("NetworkTables Info"),
"NetworkTables Info");
gNetworkTablesInfoWindow->SetView(glass::MakeFunctionView(
[&] { glass::DisplayNetworkTablesInfo(gNetworkTablesModel.get()); }));
gNetworkTablesInfoWindow->SetDefaultPos(250, 130);
gNetworkTablesInfoWindow->SetDefaultSize(750, 145);
gNetworkTablesInfoWindow->SetDefaultVisibility(glass::Window::kHide);
gNetworkTablesInfoWindow->DisableRenamePopup();
gui::AddLateExecute([] { gNetworkTablesInfoWindow->Display(); });
// NetworkTables settings window
gNetworkTablesSettings = std::make_unique<glass::NetworkTablesSettings>(
glass::GetStorageRoot().GetChild("NetworkTables Settings"));
"glass", glass::GetStorageRoot().GetChild("NetworkTables Settings"));
gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); });
gNetworkTablesSettingsWindow = std::make_unique<glass::Window>(
@@ -218,6 +226,9 @@ int main(int argc, char** argv) {
if (gNetworkTablesWindow) {
gNetworkTablesWindow->DisplayMenuItem("NetworkTables View");
}
if (gNetworkTablesInfoWindow) {
gNetworkTablesInfoWindow->DisplayMenuItem("NetworkTables Info");
}
if (gNetworkTablesLogWindow) {
gNetworkTablesLogWindow->DisplayMenuItem("NetworkTables Log");
}

View File

@@ -254,7 +254,7 @@ Storage::Value& Storage::GetValue(std::string_view key) {
} \
\
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
std::string_view key, std::span<const ArrCType> defaultVal) { \
auto& valuePtr = m_values[key]; \
bool setValue = false; \
if (!valuePtr) { \

View File

@@ -10,7 +10,7 @@
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/numbers>
#include <numbers>
#include "glass/Context.h"
#include "glass/DataSource.h"
@@ -54,7 +54,7 @@ void glass::DisplayGyro(GyroModel* m) {
// Draw the spokes at every 5 degrees and a "major" spoke every 45 degrees.
for (int i = -175; i <= 180; i += 5) {
double radians = i * 2 * wpi::numbers::pi / 360.0;
double radians = i * 2 * std::numbers::pi / 360.0;
ImVec2 direction(std::sin(radians), -std::cos(radians));
bool major = i % 45 == 0;
@@ -74,7 +74,7 @@ void glass::DisplayGyro(GyroModel* m) {
draw->AddCircleFilled(center, radius * 0.075, secondaryColor, 50);
double radians = value * 2 * wpi::numbers::pi / 360.0;
double radians = value * 2 * std::numbers::pi / 360.0;
draw->AddLine(
center - ImVec2(1, 0),
center + ImVec2(std::sin(radians), -std::cos(radians)) * radius * 0.95f,

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