Compare commits

...

196 Commits

Author SHA1 Message Date
Tyler Veness
a7173dbd3c [apriltag] Fix GCC 14 calloc() warning (#6773) 2024-06-23 20:24:45 -07:00
Tyler Veness
2ff7033edf [upstream_utils] Update to LLVM 18.1.8 (#6764) 2024-06-21 23:21:39 -07:00
Wispy
ca92ef89d3 [sim] Don't send joystick data during auto (#6732)
Testing on a Rio showed that the joystick inputs are not zeroed, they just don't update.
2024-06-21 20:56:23 -07:00
Tyler Veness
b8c2571638 [wpimath] Fix precondition violation messages in LQR and Kalman filters (#6731) 2024-06-21 20:55:01 -07:00
Tyler Veness
d2b1aa1869 [sysid] Remove CTRE v5 CANCoder preset (#6753)
Fixes #6466.
2024-06-21 20:54:29 -07:00
Gold856
76a3a60712 [ci] Bump actions versions to Node 20 (#6758)
Bump webfactory/ssh-agent to 0.9.0 (Node 20)
Switch to gradle/actions/wrapper-validation (Node 20)
Bump mozilla-actions/sccache-action to 0.0.5 (Node 20 for sanitizers only)
Bump actions/github-script to 7 (Node 20 for documentation only)
Bump wpilibsuite/import-signing-certificate to 2 (Node 20)
Bump JamesIves/github-pages-deploy-action to 4.6.1 (Node 20)
2024-06-21 20:53:51 -07:00
sciencewhiz
f5df6f88c8 [ci] Add dispatch actions task to ping tools on tag (#6755)
Allows automation of tagging of tools on allwpilib tag
Adapted from robotpy
2024-06-21 17:15:42 -07:00
Gold856
7f5970b27a [ci] Remove unused steps/package installs (#6756)
Removed steps/packages were used for protobuf/generated files
2024-06-21 11:15:42 -07:00
Tyler Veness
25865759f4 [upstream_utils] Upgrade Sleipnir and use wpi::SmallVector (#6748) 2024-06-21 11:14:19 -07:00
Tyler Veness
e2893fc1a3 [upstream_utils] Add wpi::SmallVector erase_if() (#6752) 2024-06-18 14:03:43 -06:00
PJ Reiniger
b6bd798f9e [wpilib] Pregenerate PWM motor controllers (#6742)
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2024-06-18 08:43:08 -06:00
PJ Reiniger
66c0abb732 [build] Use pathlib in pre-generation scripts (#6745) 2024-06-18 08:40:37 -06:00
Brendan Raykoff
e884221a8d [wpilib] Propagate PWMMotorController stopMotor() and disable() to followers (#6750) 2024-06-18 07:40:32 -06:00
Gold856
3a0ee5c9a7 [build] cmake: Improve OpenCV file search (#6747) 2024-06-16 17:08:48 -07:00
Ryan Blue
bb8480c690 [wpilib] Include sendable type information in topic metadata (#6741) 2024-06-14 08:12:18 -06:00
Ryan Blue
d3aa7f85dd [ci] Revert disable std::mutex constexpr constructor on Windows (#6736) 2024-06-12 14:45:59 -06:00
Gold856
3d6b710293 [wpiutil] DataLog: Don't constantly retry log creation when low on space (#6730) 2024-06-10 20:22:50 -06:00
Gold856
ae6954c78f [wpiunits] Clarify return conditions for isEquivalent (NFC) (#6727) 2024-06-09 17:54:31 -06:00
Tyler Veness
a087544933 [upstream_utils] Disable spurious maybe-uninitialized warning from GCC 14 (#6728) 2024-06-09 17:53:38 -06:00
Gold856
b9935c9885 [ci] Fix robotpy PR commenting (#6723) 2024-06-09 06:37:30 -07:00
Ryan Blue
3617a95260 [ci] Disable std::mutex constexpr constructor on Windows (#6725)
Fixes cmake builds, works around issue with windows runners.

Revert when GitHub runner images are fixed.
2024-06-09 06:36:29 -07:00
Gold856
b0cc84a9c7 [build] Upgrade to PMD 7.2.0 (#6718) 2024-06-08 22:08:23 -07:00
Gold856
e2dcbd016d [wpimath] Remove deprecated TrapezoidProfile method in C++ header (#6721) 2024-06-08 22:05:17 -07:00
Tyler Veness
72ae751b9a [ci] Fix PathWeaver name typo (#6717) 2024-06-08 11:58:45 -07:00
Tyler Veness
27cbbfe2ab [upstream_utils] Update to LLVM 18.1.7 (#6715) 2024-06-08 11:24:42 -07:00
Gold856
65c6306047 [wpilib,commands] Use Jinja to generate HID classes (#6274) 2024-06-08 09:59:07 -07:00
sciencewhiz
a0efc9ca31 [ci] Add workflow to build java tools for PRs (#6698)
This can catch breaking changes earlier.
- Builds linux only for speed
- Builds and publishes artifacts in this file as it was actually faster
then reusing the artifacts from the gradle file as those need the
combiner run on them. It uploads the artifacts to Actions with a 1 day
retention, because that allows the tools to be built in parallel, which
overall sped things up. This also only allows relevant tasks in wpilib
to be run
- The tool builds are uploaded with a shorter retention time in case
someone wants to do more extensive tests.
2024-06-08 09:55:07 -07:00
Thad House
917b5dde66 [build] Update Native Utils, remove all rpath tricks (#6671)
With fixes to Windows and macOS library path handling, all the tricks we used in the past to make library loading work are no longer required.
2024-06-08 09:52:29 -07:00
Tyler Veness
300595da9e [upstream_utils] Update Sleipnir (#6709)
Upstream now uses std::format/std::print, so we have to backport it to
fmtlib.
2024-06-08 09:50:59 -07:00
Brendan Raykoff
0606da64c9 [wpimath] LinearFilter lastValue(): Fix runtime exception (#6713) 2024-06-07 17:24:34 -07:00
Gold856
1c884b2260 [ci] Use the comment command token for robotpy comment action (#6711) 2024-06-07 16:15:30 -07:00
Gold856
5b94421b5a [ci] Fix permissions for commenting on commands PRs (#6708) 2024-06-06 10:39:58 -07:00
Sam Carlberg
ad18fa62ee [wpilib] Add LED pattern API for easily animating addressable LEDs (#6344)
Add LEDReader and LEDWriter helper interfaces to facilitate composing simple patterns into more complex ones, e.g. LEDPattern.solid(Color.kBlue).breathe(Seconds.of(0.75)). Pattern composition relies on changing out the write behavior; for example, offsetBy increments the indexes to write to; while blink will switch between playing a base pattern and turning off all the LEDs.

Add a view class for splitting a single large buffer into smaller distinct sections, which is useful for dealing with long chained LED strips mounted on different parts of a robot. Views cannot be written directly to an LED strip (in fact, trying to do so won't even compile).

Adds some utility methods to the Color class for interpolating between two colors, and support color representations with 32-bit integers to avoid object allocations.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-06-05 19:41:10 -07:00
Tyler Veness
5221069bcc [wpimath] Disambiguate wpimath JNI functions (#6695)
Each collection of JNI functions now has its own class.
2024-06-05 12:26:58 -07:00
Wispy
df4694c9df [wpinet] Add indication of success/failure to PortForwarder (#6697) 2024-06-04 21:09:45 -07:00
Gold856
b99d9c1710 Use Java 17 features (#6691)
Uses enhanced instanceof (and simplify equals methods)
Uses switch expressions and arrow labels
Seal and finalize some Shuffleboard classes

Co-authored-by: Sam Carlberg <sam@slfc.dev>
2024-06-04 21:09:10 -07:00
Joseph Eng
d6b66bfa55 [wpiutil] Add remove_prefix() and remove_suffix() (#6118) 2024-06-04 21:01:52 -07:00
Brendan Raykoff
8def7b2222 [wpimath] Add geometry classes for Rectangle2d and Ellipse2d (#6555)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-06-04 18:27:32 -07:00
Tyler Veness
afaf7e2c3f [upstream_utils] Format Eigen patches with Eigen's clang-format config (#6699) 2024-06-04 18:26:20 -07:00
Wispy
5f8c842223 [wpilib] Compressor: Add more Sendable data (#6687) 2024-06-03 07:47:17 -07:00
Tyler Veness
bdc42532ed [wpimath] Make unit max and min constexpr (#6690) 2024-06-02 21:45:02 -07:00
sciencewhiz
21e970f2cd [fieldImages] Move Java fields to edu.wpi.first.fields (#6694)
Doesn't make sense in edu.wpi.fields, and resources are already in
edu.wpi.first.fields
This will require changes in Shuffleboard and Pathweaver
2024-06-02 21:44:39 -07:00
sciencewhiz
38ee6476f2 [ci] Update cmake actions to node 20 versions (#6692) 2024-06-02 21:00:43 -07:00
Tyler Veness
a97904ed1f [upstream_utils] Fix gcem min and max compilation (#6689) 2024-06-02 15:52:12 -07:00
Joseph Eng
1828fdaaa4 [commands] Define order of parallel groups (#6602) 2024-06-01 12:01:15 -07:00
Joseph Eng
7751f6d1d2 [wpimath] Remove swerve wrappers for odometry and pose estimation, move wheel positions operations to kinematics (#6673) 2024-06-01 11:59:54 -07:00
Peter Johnson
7c8a36f3eb [wpiutil] Add Struct and Protobuf clone and immutable checks (#6686) 2024-06-01 11:58:53 -07:00
Wispy
ae8e4289b9 [dlt] Fix build.gradle description (#6682) 2024-05-31 13:43:51 -07:00
CoolSpy3
c1fc86033a [sim] Clarify Sim CAN Device Documentation (#6679) 2024-05-30 05:32:45 -07:00
Dean Brettle
9782abbcb1 [wpilib] Add protected default constructor for CallbackStore (#6667)
This  is to allow 3rd party sim providers (eg vendors) to subclass this class so that the register methods of their sim classes can return CallbackStores like the builtin sims.

Although it was already possible to create such a subclass but passing dummy parameters to one of the other constructors, this eliminates the need to pass such dummy parameters and makes it clearer that subclassing is allowed.
2024-05-27 21:19:12 -07:00
Gold856
0ad4cd69d0 [hal] Initialize DIO to true in sim (#6670) 2024-05-27 21:18:19 -07:00
Avi
76685fe7e8 [wpilibj] Fix docs typo (NFC) (#6672) 2024-05-27 21:17:41 -07:00
Dean Brettle
3b8d8a367b [wpilibj] Make CallbackStore.close() idempotent (#6666)
This is "strongly recommended" in the javadocs for the AutoCloseable.close().
2024-05-27 07:58:03 -07:00
CoolSpy3
a6ac4228c3 [sim] WebSockets Documentation Fixes (#6668) 2024-05-27 06:52:25 -07:00
CoolSpy3
98fcbdb44e [sim] Update WebSockets API Specification (#6664)
Also update AddressableLED data to be output instead of input.
2024-05-26 14:58:32 -07:00
Thad House
958387ddd3 [wpiutil] Add method to skip SetDllDirectory for windows loading (#6662)
With DependentLoadFlags, we shouldn't need this function anymore. Add a way to test this easily without needing to modify allwpilib.
2024-05-26 14:22:29 -07:00
Thad House
a2f2502f70 [build] Bump Native Utils to 2025.0.0 (#6663)
Adds DependentLoadFlags
2024-05-26 11:55:55 -07:00
Thad House
fbfef85f45 [wpiutil] Fix CombinedRuntimeLoader default path (#6661) 2024-05-26 11:12:44 -07:00
Vasista Vovveti
cdbd64c3df [ci] Give comment action PR write permission (#6594) 2024-05-25 19:42:28 -07:00
Joseph Eng
eb3635e622 [commands] Make CommandGenericHID a public base class of CommandXboxController (#6658) 2024-05-25 11:44:41 -07:00
Joseph Eng
9ef6d13b27 [wpilibj] Suppress this-escape warning on SharpIR (#6659) 2024-05-25 11:43:27 -07:00
Wispy
e2545231b8 [glass] Save input after clicking away (#6657) 2024-05-25 09:19:52 -07:00
sciencewhiz
4252a36668 DevelopmentBuilds.md: Fix missing apostrophe (#6655) 2024-05-24 19:46:11 -07:00
Jade
f1e072fc98 [commands] GenericHIDController: use composition in C++ (#6296) 2024-05-24 16:36:05 -07:00
Theinatorinator
221d568bd9 [wpilibj] Add tests for ADIS16448 simulation (#6152) 2024-05-24 16:33:25 -07:00
scarmain
c62396ce4e [wpilib] Fix PowerDistribution.GetAllCurrents() (#6025) 2024-05-24 16:31:19 -07:00
Nicholas Armstrong
f42bc45ee8 [wpilib] FlywheelSim cleanup (#6629)
This is a cleanup of the FlywheelSim class with a few added features.

- One FlywheelSim constructor that takes a plant, DCMotor, and a optional number of measurementStdDevs. The documentation now states how to construct the plant either through LinearSystemId.createFlywheelSystem or identifyVelocitySystem.

- The gearbox, gearing and moment of Inertia (J) are now private final fields. The gearing is determined from the plant in the constructor as well as the moment of inertia. There are getter methods that allow the flywheelSim to return the gearbox, gearing, and moment of inertia.

- The getCurrentDrawAmps function now uses m_x instead of getAngularVelocityRadPerSec in accordance with more accuracy and matches the patter in other sims.

- Added getter methods for the InputVoltage, angularAcceleration and torque

- (Java only) A third getter method for returning the AngularVelocity of the flywheel using a MutableMeasure as a backing field that is set when getAngularVelocity is called. This summarily returns the angularVelocity as just a Measure object. This allows the user of this class to handle unit conversions with less numerical manipulation. Alterations in C++ for this feature were not needed.
2024-05-24 16:05:14 -07:00
Wispy
8c107e4b75 [wpilibj] Add TimedRobot.addPeriodic() measure overloads (#6654) 2024-05-24 16:02:36 -07:00
Peter Johnson
652c721895 [ci] Remove build-dir from Windows Gradle build (#6269)
This is no longer required due to increased disk space in Actions.
2024-05-24 14:03:00 -07:00
Peter Johnson
7e00f2d3eb [wpilib] Fix docs for Sharp IR simulation (NFC) (#6653) 2024-05-24 14:02:22 -07:00
Joseph Eng
5c5e5af0c6 [commands] Add missing C++ decorators (#6599) 2024-05-24 13:14:15 -07:00
Dean Brettle
237ebfd0f2 [sim] Add CAN message schema to wpilib-ws.yaml and add 2 CANMotor props (#6651)
Co-authored-by: CoolSpy3 <coolspythree@gmail.com>
2024-05-24 12:56:34 -07:00
Dustin Spicuzza
dc0e9712e6 [wpilib] Add support for Sharp IR sensors (#6023) 2024-05-24 12:55:30 -07:00
Thad House
d05c7c125b [wpilib] Rewrite DutyCycleEncoder and AnalogEncoder (#6398) 2024-05-24 11:53:56 -07:00
Carl Hauser
294c9946ae [wpimath] PIDController.setIntegratorRange documentation fix (#6489) 2024-05-24 11:49:00 -07:00
Jade
6220c6be4d [wpiutil] Remove RuntimeDetector and simplify RuntimeLoader (#6600) 2024-05-24 10:48:59 -07:00
Gold856
72a6d22d9a [build] Organize cmake files (#6617) 2024-05-24 10:48:05 -07:00
Jade
8834cb1de4 [wpimath] ChassisSpeeds: add equals method (#6414) 2024-05-24 10:42:32 -07:00
Tyler Veness
ae655a3a71 Rename myRobot to developerRobot and move docs to subproject (#6283) 2024-05-24 10:41:23 -07:00
Ryan Blue
65f4505e3c [wpimath] Add constraints support to ProfiledPIDController Sendable implementation (#6354) 2024-05-24 10:39:56 -07:00
bovlb
badd090538 [wpimath] Document example usage for InterpolatingDoubleTreeMap (NFC) (#6456) 2024-05-24 10:35:42 -07:00
Gold856
2fd8dae503 [ci] Use LF line endings for pregenerate.yml (#6650) 2024-05-23 08:23:54 -07:00
Tyler Veness
27efd37c52 [upstream_utils] Upgrade Eigen to include GCC 14 patches (#6646) 2024-05-23 06:49:20 -07:00
Tyler Veness
0c822b45ab [wpimath] Fix Eigen maybe-uninitialized warnings (#6636) 2024-05-20 16:53:22 -07:00
Jade
58751338ec [ci] Remove workaround for sanitizers (#6638)
It's been added to the base image.
2024-05-20 12:20:12 -07:00
ncorrea210
0b5aec82ff [wpimath] Add ChassisSpeeds::ToTwist2d() to ChassisSpeeds (#6634)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-05-20 12:19:17 -07:00
Wispy
820f68dc08 [wpimath] Add InterpolatingDoubleTreeMap.ofEntries() (#6635)
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2024-05-20 12:18:28 -07:00
Benjamin Hall
8c420fa4c1 [wpimath] Modify C++ LinearFilter::Reset(span<double>, span<double>) to take span<T> (#6628)
Previously, the overload took a span<double>. However, m_inputs and m_outputs store type T, so the function could not be called for any T that does not have an implicit constructor from double.
2024-05-15 14:46:10 -06:00
Nicholas Armstrong
ab315e24c8 [wpimath] LinearSystemSim Constructor and method cleanup (#6502)
Modified Java constructors to take a variable number of measurement std devs argument with checks in place to make sure the right amount (or none) are passed into the constructor. All changes passed down to classes utilizing LinearSystemSim.

Removed excess constructors

Removed Java and C++ CurrentDrawAmps method as it doesn't belong in a generic (non electrical) linear system. Kept a non override version in all derived electrical classes.

Also LinearSystemSim has now been made agnostic to electrical systems. Inputs don't have to be voltage. BatteryVoltage clamp function has been pushed down to electrical subclasses.

Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-05-15 11:40:30 -06:00
Tyler Veness
0f45fe9486 [upstream_utils] Fix Eigen macro name (#6627)
I've confirmed this fixes the build with GCC 14 in C++23 mode.
2024-05-15 07:42:53 -06:00
Nicholas Armstrong
7fc17811fa [wpimath] Add full state support to LinearSystemId functions (#6554)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-05-15 07:23:22 -06:00
Wispy
7fbbecb5b7 [wpiunits] Add Measure.divide(Measure<U2>) (#6611) 2024-05-15 07:22:38 -06:00
Michael Fisher
b0d3bf4ddf [build] cmake: add absl dependency for newer protobuf (#6609)
Works around a bug on some systems where protobuf does not correctly depend on required absl libraries.
2024-05-14 21:25:15 -06:00
Tyler Veness
40b35f0d51 [upstream_utils] Fix compilation failures on constexpr matrices with GCC 14 (#6626) 2024-05-14 21:22:32 -06:00
Tyler Veness
637647b941 [upstream_utils] Improve Eigen intellisense fix (#6621) 2024-05-14 18:47:51 -06:00
Tyler Veness
0a967e0e62 [upstream_utils] Suppress deprecation warnings for Eigen's has_denorm (#6619)
std::has_denorm and std::has_denorm_loss were deprecated in C++23.
This avoids deprecation warnings with Clang 18 set to C++23.
2024-05-14 16:33:57 -06:00
Thad House
4ce8f3f935 Change C APIs to a unified string implementation (#6299)
Currently in the entire C API of WPILib we have ~8 different ways of handling strings. The C API actually isn't built for pure C callers (We don't actually have any of those). Instead, they're built for interop between languages like LabVIEW and C# which can talk to C API's directly.

For output parameters, the choice was fairly obvious. An output struct containing a const string pointer and a length makes the most sense. Its easy to use these from most other languages, and doesn't require special null termination handling. Freeing these is also easy, as if you ever receive one of these string structures, theres just a single function call to free it.

Input parameters are a bit more complex. To be used from pure C, and from LabVIEW, a null terminated string is the best in most cases. However, null terminated strings in general have a lot of downsides. Additionally, from LabVIEW there are other considerations around encoding that having a wrapper struct helps make a bit easier. From a language like C#, a wrapper struct is by far the easiest, as custom marshalling can make it trivial to marshal both UTF8 and UTF16 strings down.

The final consideration is its nice to have an identical concept for both input and output. It makes the rules fairly easy to understand.

WPILib will not have any APIs that manipulate a string allocated externally. This means WPI_String can be const, as across the boundary it is always const.
If a WPILib API takes a const WPI_String*, WPILib will not manipulate or attempt to free that string, and that string is treated as an input. It is up to the caller to handle that memory, WPILib will never hold onto that memory longer than the call.
If a WPILib API takes a WPI_String*, that string is an output. WPILib will allocate that API with WPI_AllocateString(), fill in the string, and return to the caller. When the caller is done with the string, they must free it with WPI_FreeString().
If an output struct contains a WPI_String member, that member is considered read only, and should not be explicitly freed. The caller should call the free function for that struct.
If an array of WPI_Strings are returned, each individual string is considered read only, and should not be explicitly freed. The free function for that array should be called by the caller.
If an input struct containing a WPI_String, or an input array of WPI_Strings is passed to WPILib, the individual strings will not be manipulated or freed by WPILib, and the caller owns and should free that memory.
Callbacks also follow these rules. The most common is a callback either getting passed a const WPI_String* or a struct containing a WPI_String. In both of these cases, the callback target should consider these strings read only, and not attempt to free them or manipulate them.
2024-05-13 05:35:14 -07:00
Peter Johnson
178fe99f12 [wpiutil] Split DataLog background writer into different class (#6590)
DataLog is now a base class, with DataLogBackgroundWriter being the
background thread version and DataLogWriter being a non-threaded version.

Also split the C header into a separate file to make it more wpiformat friendly.
2024-05-12 14:09:43 -07:00
Gold856
305a0657e2 [cscore] Deprecate AxisCamera (#6579) 2024-05-12 10:28:51 -07:00
sciencewhiz
fb3e0e1ecb [docs] Update readme to say git clone is required (#6603) 2024-05-12 10:27:56 -07:00
Tyler Veness
2e828ae053 [upstream_utils] Fix fmtlib tautological-compare warning from GCC 14 (#6613)
The patch is from upstream.
2024-05-12 10:27:01 -07:00
Tyler Veness
d3af27be94 [wpiunits] Fix Javadoc warning (#6614) 2024-05-12 10:25:44 -07:00
sciencewhiz
4cb2edbb98 [docs] Run Doc lint with JDK 21 (#6612) 2024-05-12 07:55:15 -07:00
Tyler Veness
d88c71ffdc [wpiutil] Upgrade to fmt 10.2.1, add wpi::print (#6161)
We now use a wrapper (wpi::print) to catch exceptions since we can't patch
std::print() to not throw when we ultimately migrate to it.

fmtlib and std format/print throw the same exceptions and always have. We previously patched fmt::print() to not throw a write failure exception, but we can't do that for std::print(); wpi::print() is the migration plan.
2024-05-12 06:25:42 -07:00
Sam Carlberg
6c9dcc157e [wpiunits] Change units to track their base unit, instead of their base class (#6342)
Unit objects now have a reference to the base unit from which they're derived. Constructing a unit object without specifying a base unit implicitly signifies that it's its own base unit, eg new Angle(null, 1, "Radian", "rad") would be the base angle unit of radians, while new Angle(Radians, 2 * PI, "Rotation", "R") would be a new angle unit based on radians.

This fixes much of the hacky code surrounding the derived unit types Velocity, Per, and Mult, but is a breaking change for any user code that defines custom unit classes or uses the anonymous unit type.
2024-05-12 06:15:56 -07:00
Tyler Veness
dc00a13d83 [wpimath] Make cost and covariance matrix functions constexpr (#6444) 2024-05-07 15:32:14 -07:00
Tyler Veness
bdc7344df1 [upstream_utils] Upgrade Eigen to get more constexpr support (#6596) 2024-05-07 12:47:15 -07:00
Isaac Turner
a6dd95eb9e [wpilib] Remove deprecated Gyro interface (#6567) 2024-05-06 21:54:39 -07:00
Wispy
2563ff9f18 [wpimath] Add Pair.equals() (#6580) 2024-05-06 21:52:59 -07:00
sciencewhiz
f77d01c085 [docs] Build javadocs with JDK 17 (#6588)
Remove JDK 11 javadocs workarounds.
Also update readme to say to install JDK 17.
2024-05-06 21:52:22 -07:00
Kaya
408980462f [commands] CommandScheduler: Provide function to print watchdog epochs (#6582) 2024-05-06 15:53:14 -07:00
Ryan Blue
2e71e85b8d switch CI to temurin jdk (#6592) 2024-05-06 13:37:09 -07:00
Isaac Turner
6a73ca8c08 [build] Change source compatibility to Java 17 (#6585) 2024-05-05 07:52:42 -07:00
Tyler Veness
9ed2f66914 [build] Bump macOS deployment target to 13 (#6548)
macOS 12 is EOL in mid-October of 2024 based on when macOS 11 was
dropped last year.
2024-05-04 11:11:10 -07:00
Isaac Turner
d3060d8eba [wpilib] IterativeRobotBase: Provide function to print watchdog epochs (#6581) 2024-05-04 10:19:36 -07:00
Vasista Vovveti
27babe5584 [ci] Comment on command PRs to open a RobotPy PR (#6574) 2024-05-04 08:37:45 -07:00
Ryan Blue
7596aeda10 [wpilib] GenericHID.setRumble: Fix Java integer overflow (#6529) 2024-05-04 08:36:40 -07:00
Isaac Turner
c76b358290 [ci] Update gradle wrapper validation action version (#6335) 2024-05-03 12:41:47 -07:00
Tim Winters
bad56bcbe8 [commands] Add StartRun command factory (#6572) 2024-05-03 12:40:13 -07:00
Tyler Veness
e172aa66f7 [wpimath] Java: add static instantiations of common rotations (#6563)
C++ doesn't need this because it supports value types, which are much
cheaper to construct. constexpr is also available to make construction
zero-cost.
2024-05-03 12:39:35 -07:00
Isaac Turner
9c7120e6bf [wpilibc] Remove deprecated Joystick angle functions (#6569) 2024-05-03 12:38:58 -07:00
Drew Williams
0afc35f336 [commands] Fix C++ SysIdRoutine crashing when passed nullptr or {} (#6508)
Flattens parameter from a `std::optional<std::function<...>>` to just a `std::function<...>`.  This is a breaking change but a trivial one for teams to fix.
2024-05-01 09:09:15 -07:00
Tyler Veness
ae4bcefefc [wpimath] Fix incorrect docs for Rotation3d default constructor (#6571)
A Rotation3d is not defined by one angle.
2024-05-01 09:06:13 -07:00
Isaac Turner
513d1a0a15 [wpilib] Remove deprecated Accelerometer interface (#6568) 2024-05-01 09:04:50 -07:00
Tyler Veness
a85e7693de [examples] Use UDL for turns (#6570) 2024-05-01 09:02:18 -07:00
Tyler Veness
5359112b15 [wpimath] Deprecate RamseteController (#6494)
LTVUnicycleController is a drop-in replacement with better tuning knobs.

The RamseteCommand examples were removed instead of retrofitted with
LTVUnicycleController because we're planning on removing the command
controller classes anyway, so it would be wasted effort. The
SimpleDifferentialDriveSimulation example shows direct
LTVUnicycleController usage.
2024-04-29 22:01:42 -07:00
Tyler Veness
7601b7250a [upstream_utils] Upgrade Sleipnir to use a small vector type (#6565)
This sped up ArmFeedforward.Calculate() by up to 2x.
2024-04-29 22:00:32 -07:00
DeltaDizzy
a9cfd0d0f9 [commands] Deprecate proxy supplier constructor (#6553) 2024-04-29 21:11:29 -07:00
Isaac Turner
c71db8ea9c [wpiutil] Remove old InterpolatingTreeMap location (#6560) 2024-04-29 21:05:12 -07:00
Isaac Turner
70417f64da [wpimath] Remove deprecated TrapezoidProfile constructors (#6558) 2024-04-29 21:04:57 -07:00
Isaac Turner
f5e08652f8 [wpimath] Remove deprecated MatBuilder factory (#6557) 2024-04-29 21:04:43 -07:00
Thad House
eec99eb653 [wpilibj] Fix AsynchronousInterrupt (#6564) 2024-04-29 21:04:24 -07:00
Isaac Turner
9cae707065 [wpilib] Remove deprecated SetHandler function (#6556) 2024-04-29 21:03:45 -07:00
Isaac Turner
0f8aa8aedf [commands] Remove CommandBase (#6545) 2024-04-28 12:04:51 -07:00
Ryan Heuer
ac32f921f6 [glass] Add math expression input for NetworkTables numerical values (#6530) 2024-04-28 12:03:49 -07:00
Isaac Turner
67fe11f9cd [commands] Rename deadlineWith to deadlineFor (#6544)
Deprecate deadlineWith for backwards compatibility.
2024-04-28 12:02:29 -07:00
Nicholas Armstrong
1ec089c7f9 [wpimath] Add ArmFeedforward calculate() overload that takes current and next velocity instead of acceleration (#6540)
Co-authored-by: Tyler Veness <calcmogul@gmail.com>
2024-04-28 12:01:08 -07:00
Isaac Turner
1727c74b80 [ntcore] remove deprecated flag (#6551) 2024-04-28 06:17:39 -07:00
Isaac Turner
890185acee [ntcore] Remove deprecated delete function (#6552) 2024-04-28 06:15:19 -07:00
Tyler Veness
fd363fdf5a [wpimath] Add Sleipnir (#6541)
This is useful for solving quadratic programs.
2024-04-27 22:42:42 -07:00
DeltaDizzy
1e4a647918 [commands] Disambiguate ProxyCommand and DeferredCommand (#6324) 2024-04-27 22:41:04 -07:00
Wispy
39d33bfca6 [wpiunits] Adds FeetPerSecondPerSecond (#6543) 2024-04-27 21:08:13 -07:00
Isaac Turner
5edc652c05 [commands] Fix multiple C++ warnings (#6546) 2024-04-27 21:07:50 -07:00
Isaac Turner
d9eb3691d8 [commands] Remove deprecated TrapzoidProfileCommand api (#6547) 2024-04-27 21:07:23 -07:00
Tyler Veness
a42ffb8fa4 [ci] Bump wpiformat to 2024.34 (#6549) 2024-04-27 21:06:57 -07:00
Joseph Eng
d4e6a068ac [apriltag] Deprecate loadAprilTagLayoutField() (#6550) 2024-04-27 21:04:30 -07:00
Joseph Eng
cdfa2ece6f [wpimath] Add PoseEstimator.sampleAt() (#6426) 2024-04-27 21:03:37 -07:00
Isaac Turner
962bf7ff10 [ntcore] Backup persistent file if it fails to parse (#6523) 2024-04-27 07:04:27 -07:00
DeltaDizzy
2cd3935aa8 [sysid] Remove obsolete WPILib presets, rename CTRE presets (#6460) 2024-04-27 06:55:19 -07:00
Isaac Turner
427b7dcc11 README: Add python to languages (#6542) 2024-04-26 10:38:34 -07:00
Tyler Veness
e73050a8fa [wpimath] Templatize formatters (#6535)
This is required for compatibility with libc++'s std::format.
2024-04-25 16:08:24 -07:00
Nicholas Armstrong
3e6c0d0b71 [wpiunits] Add Acceleration and MOI Units (#6495) 2024-04-22 19:09:18 -07:00
Jacob Hotz
dc4c63568a [commands] Add Trigger.onChange() (#6390) 2024-04-21 22:37:15 -07:00
Ryan Blue
b620b6a4dd [apriltag] Cache layout loaded from AprilTagFields resource json (#6385) 2024-04-21 22:36:22 -07:00
Isaac Turner
d7dfa63ae9 [commands] WaitCommand: add Measure<Time> overload (#6386)
Also add waitTime() factory.
2024-04-21 22:34:52 -07:00
vichik
e89c8c1008 [wpiunits] Add isNear function implementation (#6396)
This implementation uses a tolerance in the same units as the measure it checks.
2024-04-21 20:40:33 -07:00
Juan Jose Chong
abfe2488ff [wpilib] Add flash update capability to ADI IMUs (#6450) 2024-04-21 20:39:53 -07:00
Gold856
3e5187ff32 [wpilibj] DataLogManager: Fix behavior when low on space (#6486)
Uses getUsableSpace in Java, matching how C++ determines available space (C++ calls it available, but they mean the same thing.) This fixes a bug where logs wouldn't get deleted due to incorrect available space detection.

The DataLog thread now also checks if the state was marked as stopped after a call to StartLogFile.
2024-04-21 20:34:05 -07:00
DeltaDizzy
7bc0380694 [commands] WrappedCommand: Call wrapped command initSendable (#6471) 2024-04-21 20:30:43 -07:00
Starlight220
98d2f45fa9 [commands] Fix double composition error truncation (#6501) 2024-04-21 20:28:04 -07:00
Isaac Turner
d14dfed828 [wpimath] Rotation2d: add Measure<Angle> getter (Java) (#6492) 2024-04-21 20:23:36 -07:00
HarryXChen
f26adc556d [examples] Fix memory over-allocation in Apriltag examples (#6517)
Change hamming distance to 1, add comment about memory usage.
2024-04-21 20:21:48 -07:00
Gold856
3c14d87006 Update README-CMAKE.md (#6522) 2024-04-21 20:21:07 -07:00
Isaac Turner
77536e68f0 README.md: Link straight to contributing in contents (#6525)
Avoids need to click twice to get to contributing.md.
2024-04-21 20:19:53 -07:00
Peter Johnson
c88be31ec2 Merge branch 'development' 2024-04-21 20:15:51 -07:00
Tyler Veness
74f648689e [build] Add exports to CMake subprojects (#6505)
This allows consuming allwpilib via FetchContent.
2024-04-10 22:04:22 -07:00
Tyler Veness
2def62a1ef [wpimath] Document ChassisSpeeds::Discretize() math (NFC) (#6509) 2024-04-10 22:03:44 -07:00
Tyler Veness
3a5d24ab1d [ci] Revert "Use mirror repository for liblzma (#6499)" (#6506)
This reverts commit c46847b32a.
2024-04-10 10:04:15 -07:00
Isaac Turner
02c78bc9b6 MAINTAINERS.md: Remove reference to marketplace (#6470) 2024-04-04 09:21:53 -07:00
Carl Hauser
998340296d [wpilibj] Fix EncoderSim.setDistancePerPulse parameter name and comment (NFC) (#6481) 2024-04-04 09:21:05 -07:00
Tyler Veness
d26e6d9ecc [wpimath] Support formatting Eigen array types (#6496) 2024-04-04 09:19:50 -07:00
Nicholas Armstrong
fbb3669546 [wpilib] LinearSystemSim: Add missing clamp function and getInput() (#6493) 2024-04-04 09:19:13 -07:00
Ryan Blue
c46847b32a [ci] Use mirror repository for liblzma (#6499)
Uses https://github.com/bminor/xz to work around suspended repository.

We will revert this once vcpkg updates to point to an accessible repo.
2024-04-04 09:18:06 -07:00
Peter Johnson
33f12f0e31 Revert "[commands] Cache button and POV triggers"
Also revert the associated formatting commit.  This was an accidental merge.
This reverts commit ff929d4a5f.
This reverts commit 2392c9f278.
2024-03-26 00:08:07 -07:00
sciencewhiz
25ad6eafd5 Add reference to development to CONTRIBUTING.md (#6467) 2024-03-24 23:52:36 -07:00
Isaac Turner
84a55d13f3 [ci] Fix 2023 docker image usage (#6459) 2024-03-24 23:51:38 -07:00
Wispy
4a548935d3 [wpimath] Add Pair.toString() (#6463) 2024-03-24 23:51:08 -07:00
Tyler Veness
85ea5f8497 [wpimath] Make more LinearSystemId functions not throw if Kv = 0 (#6465) 2024-03-24 23:50:41 -07:00
Gold856
c32e7db8e3 [wpiutil] DataLog: Don't constantly retry creating logs when low on space (#6468)
When low on space, a log file won't be created. This is detected as a "deletion", and the DataLog thread will continously try to create a log, fail to do so because of low space, detect it as a "deletion", and do so in a loop.

If there's not enough space, the DataLog will be marked as stopped, preventing this infinite loop. Calls to start() will hit this code path and mark it as stopped again.
2024-03-24 23:50:18 -07:00
Tyler Veness
2392c9f278 Run java format (#6462) 2024-03-22 09:50:28 -07:00
Peter Johnson
0dbdbb2aac Merge branch 'main' into development 2024-03-20 22:54:42 -07:00
Tyler Veness
b38540e1af [ci] Pin wpiformat version in comment command (#6458) 2024-03-20 22:53:52 -07:00
Peter Johnson
ff929d4a5f [commands] Cache button and POV triggers
This is a common footgun for teams.
2024-03-20 22:52:12 -07:00
Tyler Veness
b2113d3a9a [ci] Pin wpiformat version in comment command (#6457) 2024-03-20 22:50:55 -07:00
Tyler Veness
5370f249a1 [build] Upgrade to wpiformat 2024.33 (#6449)
This upgrades to clang-format and clang-tidy 18.1.1. This has the
constructor attribute formatting fix, so we can remove our
WPI_DEPRECATED macro.
2024-03-18 23:11:20 -07:00
Peter Johnson
74057543aa [glass] Don't limit window name+label to 128 chars (#6447) 2024-03-18 14:28:00 -07:00
Tyler Veness
b4674bacb9 [wpiutil] Upgrade to LLVM 18.1.1 (#6405) 2024-03-17 18:39:03 -07:00
Peter Johnson
fd4424eb89 Merge branch 'main' into development 2024-03-16 11:06:44 -07:00
Peter Johnson
c10f7d91b7 [ci] Work around asan actions bug (#6442) 2024-03-16 11:06:01 -07:00
person4268
0b13459469 [commands] Trigger: pass m_loop to new Trigger in composition functions (#6441) 2024-03-15 12:05:48 -07:00
Sam Richter
a1af2357e8 [ntcore] Fix memory leak in WebSocketConnection (#6439) 2024-03-15 11:50:30 -07:00
Peter Johnson
38c128fe6a Merge branch 'main' into development 2024-03-09 09:57:55 -08:00
Thad House
9ed0631ec9 [cscore] Add BGRA support (#6365) 2024-02-12 23:42:17 -08:00
Thad House
fb947fe998 [cscore] Use Raw for CvSink and CvSource (#6364)
Eventually we want to get to a point where we can remove OpenCV from the internals of cscore. The start to doing that is converting the existing CvSource and CvSink methods to RawFrame.

For CvSource, this is 100% a free operation. We can do everything the existing code could have done (with one small exception we can fairly easily fix).

For CvSink, by defaut this change would incur one extra copy, but no extra allocations. A set of direct methods were added to CvSink to add a method to avoid this extra copy.
2024-02-12 22:33:03 -08:00
Tyler Veness
4f9d73783b [wpimath] Make units math functions constexpr (#6345) 2024-02-05 22:43:12 -08:00
Tyler Veness
3b2a2381b6 [ci] Upgrade to new macOS runner (#6328) 2024-02-04 10:38:23 -08:00
1235 changed files with 46019 additions and 21942 deletions

View File

@@ -28,6 +28,11 @@ AlignConsecutiveMacros:
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
@@ -141,6 +146,7 @@ IntegerLiteralSeparator:
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
@@ -201,6 +207,7 @@ RawStringFormats:
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
@@ -218,6 +225,7 @@ SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
@@ -232,23 +240,28 @@ SpaceBeforeParensOptions:
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: c++20
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME

View File

@@ -1,5 +1,5 @@
color: false
definitions: []
definitions: [cmake/modules]
line_length: 100
list_expansion: favour-inlining
quiet: false

5
.gitattributes vendored
View File

@@ -3,11 +3,10 @@
*.h text eol=lf
*.inc text eol=lf
*.java text eol=lf
*.jinja text eol=lf
*.json text eol=lf
*.md text eol=lf
*.xml text eol=lf
# Generated files
hal/src/generated/** linguist-generated
ntcore/src/generated/** linguist-generated
wpimath/src/generated/** linguist-generated
*/src/generated/** linguist-generated

View File

@@ -16,11 +16,11 @@ jobs:
name: Linux
container: wpilib/roborio-cross-ubuntu:2024-22.04
flags: "-DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
- os: macOS-12
- os: macOS-14
name: macOS
container: ""
env: "PATH=\"/usr/local/opt/protobuf@3/bin:$PATH\""
flags: "-DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON -DCMAKE_LIBRARY_PATH=/usr/local/opt/protobuf@3/lib -DProtobuf_INCLUDE_DIR=/usr/local/opt/protobuf@3/include -DProtobuf_PROTOC_EXECUTABLE=/usr/local/opt/protobuf@3/bin/protoc"
env: "PATH=\"/opt/homebrew/opt/protobuf@3/bin:$PATH\""
flags: "-DCMAKE_BUILD_TYPE=Release -DWITH_JAVA=OFF -DWITH_EXAMPLES=ON -DCMAKE_LIBRARY_PATH=/opt/homebrew/opt/protobuf@3/lib -DProtobuf_INCLUDE_DIR=/opt/homebrew/opt/protobuf@3/include -DProtobuf_PROTOC_EXECUTABLE=/opt/homebrew/opt/protobuf@3/bin/protoc"
name: "Build - ${{ matrix.name }}"
runs-on: ${{ matrix.os }}
@@ -28,27 +28,14 @@ jobs:
steps:
- 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 libprotobuf-dev protobuf-compiler ninja-build
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java libprotobuf-dev protobuf-compiler ninja-build
- name: Install QuickBuffers (Linux)
if: runner.os == 'Linux'
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf_1.3.3_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.3_amd64.deb
- name: Install opencv (macOS)
- name: Install dependencies (macOS)
run: brew install opencv protobuf@3 ninja
if: runner.os == 'macOS'
- name: Set up Python 3.8 (macOS)
if: runner.os == 'macOS'
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Install jinja
run: python -m pip install jinja2
uses: mozilla-actions/sccache-action@v0.0.5
- uses: actions/checkout@v4
@@ -71,21 +58,18 @@ jobs:
name: "Build - Windows"
runs-on: windows-2022
steps:
- uses: ilammy/msvc-dev-cmd@v1
- uses: ilammy/msvc-dev-cmd@v1.13.0
- name: Install CMake
uses: lukka/get-cmake@v3.27.6
uses: lukka/get-cmake@v3.29.3
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Install jinja
run: python -m pip install jinja2
uses: mozilla-actions/sccache-action@v0.0.5
- uses: actions/checkout@v4
- name: Run vcpkg
uses: lukka/run-vcpkg@v11.1
uses: lukka/run-vcpkg@v11.5
with:
vcpkgDirectory: ${{ runner.workspace }}/vcpkg
vcpkgGitCommitId: 37c3e63a1306562f7f59c4c3c8892ddd50fdf992 # HEAD on 2024-02-24

View File

@@ -0,0 +1,26 @@
name: Comment on PR for robotpy
on:
pull_request_target:
types:
- opened
paths:
- 'wpilibNewCommands/**'
jobs:
comment:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'This PR modifies commands. Please open a corresponding PR in [Python Commands](https://github.com/robotpy/robotpy-commands-v2/) and include a link to this PR.'
})

View File

@@ -33,17 +33,17 @@ jobs:
env:
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
NUMBER: ${{ github.event.issue.number }}
- name: Set up Python 3.8
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: '3.10'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: 17
- name: Install wpiformat
run: pip3 install wpiformat
run: pip3 install wpiformat==2024.34
- name: Run wpiformat
run: wpiformat
- name: Run spotlessApply

View File

@@ -22,8 +22,8 @@ jobs:
persist-credentials: false
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 13
distribution: 'temurin'
java-version: 17
- name: Set environment variables (Development)
run: |
echo "BRANCH=development" >> $GITHUB_ENV
@@ -41,11 +41,11 @@ 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.8.0
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.GH_DEPLOY_KEY }}
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.1
uses: JamesIves/github-pages-deploy-action@v4.6.1
with:
ssh-key: true
repository-name: wpilibsuite/wpilibsuite.github.io
@@ -54,7 +54,7 @@ jobs:
single-commit: true
folder: docs/build/docs
- name: Trigger Workflow
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DISPATCH_PAT_TOKEN }}
script: |

View File

@@ -11,4 +11,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/actions/wrapper-validation@v3

View File

@@ -61,7 +61,7 @@ jobs:
build-host:
env:
MACOSX_DEPLOYMENT_TARGET: 12
MACOSX_DEPLOYMENT_TARGET: 13
strategy:
fail-fast: false
matrix:
@@ -72,40 +72,34 @@ jobs:
task: "build"
build-options: "-PciDebugOnly"
outputs: "build/allOutputs"
build-dir: "c:\\work"
- os: windows-2022
artifact-name: Win64Release
architecture: x64
build-options: "-PciReleaseOnly"
task: "copyAllOutputs"
outputs: "build/allOutputs"
build-dir: "c:\\work"
- os: windows-2022
artifact-name: WinArm64Debug
architecture: x64
task: "build"
build-options: "-PciDebugOnly -Pbuildwinarm64 -Ponlywindowsarm64"
outputs: "build/allOutputs"
build-dir: "c:\\work"
- os: windows-2022
artifact-name: WinArm64Release
architecture: x64
build-options: "-PciReleaseOnly -Pbuildwinarm64 -Ponlywindowsarm64"
task: "copyAllOutputs"
outputs: "build/allOutputs"
build-dir: "c:\\work"
- os: macOS-12
- os: macOS-14
artifact-name: macOS
architecture: x64
architecture: aarch64
task: "build"
outputs: "build/allOutputs"
build-dir: "."
- os: windows-2022
artifact-name: Win32
architecture: x86
task: ":ntcoreffi:build"
outputs: "ntcoreffi/build/outputs"
build-dir: "c:\\work"
name: "Build - ${{ matrix.artifact-name }}"
runs-on: ${{ matrix.os }}
steps:
@@ -114,11 +108,11 @@ jobs:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: 17
architecture: ${{ matrix.architecture }}
- name: Import Developer ID Certificate
uses: wpilibsuite/import-signing-certificate@v1
uses: wpilibsuite/import-signing-certificate@v2
with:
certificate-data: ${{ secrets.APPLE_CERTIFICATE_DATA }}
certificate-passphrase: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -138,21 +132,16 @@ jobs:
- name: Set Java Heap Size
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
if: matrix.artifact-name == 'Win32'
- name: Configure build directory (Windows)
run: xcopy . ${{ matrix.build-dir }} /i /s /e /h /q
if: matrix.os == 'windows-2022'
- name: Check disk free space (Windows)
run: wmic logicaldisk get caption, freespace
if: matrix.os == 'windows-2022'
- name: Build with Gradle
run: ./gradlew ${{ matrix.task }} --build-cache -PbuildServer -PskipJavaFormat ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
working-directory: ${{ matrix.build-dir }}
env:
ARTIFACTORY_PUBLISH_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PUBLISH_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
- name: Sign Libraries with Developer ID
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
working-directory: ${{ matrix.build-dir }}
if: |
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
@@ -162,7 +151,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: ${{ matrix.build-dir }}/${{ matrix.outputs }}
path: ${{ matrix.outputs }}
build-documentation:
name: "Build - Documentation"
@@ -173,7 +162,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: 17
- name: Set release environment variable
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
@@ -235,7 +224,7 @@ jobs:
github.repository_owner == 'wpilibsuite' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: 17
- name: Combine (Main)
if: |
@@ -262,3 +251,18 @@ jobs:
with:
name: Maven
path: ~/releases
dispatch:
name: dispatch
needs: [combine]
runs-on: ubuntu-22.04
steps:
- uses: peter-evans/repository-dispatch@v3
if: |
github.repository_owner == 'wpilibsuite' &&
startsWith(github.ref, 'refs/tags/v')
with:
token: ${{ secrets.TOOL_REPO_ACCESS_TOKEN }}
repository: wpilibsuite/smartdashboard
event-type: tag
client-payload: '{"package_name": "allwpilib", "package_version": "${{ github.ref_name }}"}'

View File

@@ -22,12 +22,12 @@ jobs:
run: |
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.8
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: '3.10'
- name: Install wpiformat
run: pip3 install wpiformat==2024.32
run: pip3 install wpiformat==2024.34
- name: Run
run: wpiformat
- name: Check output
@@ -51,7 +51,7 @@ jobs:
tidy:
name: "clang-tidy"
runs-on: ubuntu-22.04
container: wpilib/roborio-cross-ubuntu:2023-22.04
container: wpilib/roborio-cross-ubuntu:2024-22.04
steps:
- uses: actions/checkout@v4
with:
@@ -61,12 +61,12 @@ jobs:
git config --global --add safe.directory /__w/allwpilib/allwpilib
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.8
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: '3.10'
- name: Install wpiformat
run: pip3 install wpiformat
run: pip3 install wpiformat==2024.34
- name: Create compile_commands.json
run: |
./gradlew generateCompileCommands -Ptoolchain-optional-roboRio
@@ -107,7 +107,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: 'temurin'
java-version: 21
- name: Build with Gradle
run: ./gradlew docs:zipDocs -PbuildServer -PdocWarningsAsErrors ${{ env.EXTRA_GRADLE_ARGS }}

View File

@@ -1,46 +1,50 @@
name: Check Pregenerated Files
on:
pull_request:
push:
branches-ignore:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
update:
name: "Update"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install jinja
run: python -m pip install jinja2
- name: Install protobuf dependencies
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
- name: Run hal
run: ./hal/generate_usage_reporting.py
- name: Run ntcore
run: ./ntcore/generate_topics.py
- name: Run wpimath
run: ./wpimath/generate_numbers.py && ./wpimath/generate_quickbuf.py protoc protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output
run: git --no-pager diff --exit-code HEAD
- name: Generate diff
run: git diff HEAD > pregenerated-files-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v4
with:
name: pregenerated-files-fixes
path: pregenerated-files-fixes.patch
if: ${{ failure() }}
name: Check Pregenerated Files
on:
pull_request:
push:
branches-ignore:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
update:
name: "Update"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install jinja
run: python -m pip install jinja2
- name: Install protobuf dependencies
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
- name: Run hal
run: ./hal/generate_usage_reporting.py
- name: Run ntcore
run: ./ntcore/generate_topics.py
- name: Run wpimath
run: ./wpimath/generate_numbers.py && ./wpimath/generate_quickbuf.py --quickbuf_plugin=protoc-gen-quickbuf-1.3.3-linux-x86_64.exe
- name: Run HIDs
run: ./wpilibj/generate_hids.py && ./wpilibc/generate_hids.py && ./wpilibNewCommands/generate_hids.py
- name: Run PWM Controllers
run: ./wpilibj/generate_pwm_motor_controllers.py && ./wpilibc/generate_pwm_motor_controllers.py
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output
run: git --no-pager diff --exit-code HEAD
- name: Generate diff
run: git diff HEAD > pregenerated-files-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v4
with:
name: pregenerated-files-fixes
path: pregenerated-files-fixes.patch
if: ${{ failure() }}

View File

@@ -29,17 +29,10 @@ jobs:
container: wpilib/roborio-cross-ubuntu:2024-22.04
steps:
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java python-is-python3 clang-14 libprotobuf-dev protobuf-compiler ninja-build
- name: Install QuickBuffers
if: runner.os == 'Linux'
run: wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf_1.3.3_amd64.deb && sudo apt install ./protoc-gen-quickbuf_1.3.3_amd64.deb
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java clang-14 libprotobuf-dev protobuf-compiler ninja-build
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Install jinja
run: python -m pip install jinja2
uses: mozilla-actions/sccache-action@v0.0.5
- uses: actions/checkout@v4

144
.github/workflows/tools.yml vendored Normal file
View File

@@ -0,0 +1,144 @@
name: Tools
on: [pull_request, push]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
YEAR: 2024
jobs:
build-artifacts:
name: "Build - WPILib"
runs-on: ubuntu-22.04
env:
DISPLAY: ':10'
steps:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: false
docker-images: false
swap-storage: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build WPILib with Gradle
uses: addnab/docker-run-action@v3
with:
image: wpilib/roborio-cross-ubuntu:2024-22.04
options: -v ${{ github.workspace }}:/work -w /work -e GITHUB_REF -e CI -e DISPLAY
run: df . && rm -f semicolon_delimited_script && ./gradlew :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
- uses: actions/upload-artifact@v4
with:
name: MavenArtifacts
path: |
development
retention-days: 1
Robotbuilder:
name: "Build - RobotBuilder"
needs: [build-artifacts]
runs-on: ubuntu-22.04
env:
DISPLAY: ':10'
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/robotbuilder
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- name: Patch RobotBuilder to use local development
run: cd src/main/resources/export && echo "wpi.maven.useLocal = false" >> java/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> java/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.maven.useLocal = false" >> cpp/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> cpp/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> cpp/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> cpp/build.gradle
- name: Install and run xvfb
run: sudo apt-get update && sudo apt-get install -y xvfb && Xvfb $DISPLAY &
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Build RobotBuilder with Gradle
run: ./gradlew build test --tests 'robotbuilder.exporters.*' -x htmlSanityCheck -PbuildServer -PreleaseMode ; cat build/test-results/test/TEST-robotbuilder.exporters.*.xml ;
- name: Summarize RobotBuilder Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: |
build/test-results/test/TEST*.xml
- uses: actions/upload-artifact@v4
if: always()
with:
name: TestResults
path: |
build/reports/
- uses: actions/upload-artifact@v4
with:
name: RobotBuilder Build
path: |
build/libs/
retention-days: 7
Shuffleboard:
name: "Build - Shuffleboard"
needs: [build-artifacts]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/shuffleboard
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- name: Install dependencies
run: sudo apt-get install -y libgtk2.0-0
- name: Build with Gradle
run: ./gradlew build -x Javadoc
- uses: actions/upload-artifact@v4
with:
name: Shuffleboard Build
path: |
build/allOutputs/
retention-days: 7
PathWeaver:
name: "Build - PathWeaver"
needs: [build-artifacts]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/PathWeaver
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- name: Build with Gradle
run: ./gradlew build
- uses: actions/upload-artifact@v4
with:
name: PathWeaver Build
path: |
build/allOutputs/
retention-days: 7

View File

@@ -70,6 +70,10 @@ jobs:
run: |
cd upstream_utils
./update_protobuf.py
- name: Run update_sleipnir.py
run: |
cd upstream_utils
./update_sleipnir.py
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output

View File

@@ -20,6 +20,7 @@ generatedFileExclude {
simulation/gz_msgs/src/include/simulation/gz_msgs/msgs\.h$
fieldImages/src/main/native/resources/
apriltag/src/test/resources/
wpilibc/src/generated/
}
repoRootNameOverride {

View File

@@ -64,7 +64,7 @@ option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
option(WITH_NTCORE "Build ntcore" ON)
option(WITH_WPIMATH "Build wpimath" ON)
option(WITH_WPIUNITS "Build wpiunits" ON)
option(WITH_WPILIB "Build hal, wpilibc/j, and myRobot (needs OpenCV)" ON)
option(WITH_WPILIB "Build hal, wpilibc/j, and developerRobot (needs OpenCV)" ON)
option(WITH_EXAMPLES "Build examples" OFF)
option(WITH_TESTS "Build unit tests (requires internet connection)" ON)
option(WITH_GUI "Build GUI items" ON)
@@ -198,6 +198,9 @@ endif()
set(include_dest include)
set(java_lib_dest java)
set(jni_lib_dest jni)
if(WITH_JAVA)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
endif()
if(USE_SYSTEM_LIBUV)
set(LIBUV_SYSTEM_REPLACE
@@ -284,7 +287,7 @@ set(CMAKE_EXE_LINKER_FLAGS_ASAN
set(CMAKE_SHARED_LINKER_FLAGS_ASAN
"${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=address"
CACHE STRING
"Linker lags to be used to create shared libraries for Asan build type."
"Linker flags to be used to create shared libraries for Asan build type."
FORCE
)
@@ -312,7 +315,7 @@ set(CMAKE_EXE_LINKER_FLAGS_TSAN
set(CMAKE_SHARED_LINKER_FLAGS_TSAN
"${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=thread"
CACHE STRING
"Linker lags to be used to create shared libraries for Tsan build type."
"Linker flags to be used to create shared libraries for Tsan build type."
FORCE
)
@@ -340,7 +343,7 @@ set(CMAKE_EXE_LINKER_FLAGS_UBSAN
set(CMAKE_SHARED_LINKER_FLAGS_UBSAN
"${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -fsanitize=undefined"
CACHE STRING
"Linker lags to be used to create shared libraries for Ubsan build type."
"Linker flags to be used to create shared libraries for Ubsan build type."
FORCE
)
@@ -405,7 +408,7 @@ if(WITH_WPILIB)
if(WITH_EXAMPLES)
add_subdirectory(wpilibcExamples)
endif()
add_subdirectory(myRobot)
add_subdirectory(developerRobot)
endif()
if(WITH_SIMULATION_MODULES AND NOT WITH_EXTERNAL_HAL)

View File

@@ -80,6 +80,8 @@ xₖ₊₁ = Axₖ + Buₖ
Changes should be submitted as a Pull Request against the main branch of WPILib. For most changes, commits will be squashed upon merge. For particularly large changes, multiple commits are ok, but assume one commit unless asked otherwise. We may ask you to break a PR into multiple standalone PRs or commits for rebase within one PR to separate unrelated changes. No change will be merged unless it is up to date with the current main branch. We do this to make sure that the git history isn't too cluttered.
During the build season, breaking changes or other changes intended for the next season can be created as a pull request against the development branch of WPILib. After the season is over, the changes in the development branch will be merged into main.
### Merge Process
When you first submit changes, GitHub Actions will attempt to run `./gradlew check` on your change. If this fails, you will need to fix any issues that it sees. Once Actions passes, we will begin the review process in more earnest. One or more WPILib team members will review your change. This will be a back-and-forth process with the WPILib team and the greater community. Once we are satisfied that your change is ready, we will allow our hosted instance to test it. This will run the full gamut of checks, including integration tests on actual hardware. Once all tests have passed and the team is satisfied, we will merge your change into the WPILib repository.

View File

@@ -19,7 +19,7 @@ To build a project using a development build, find the build.gradle file and ope
wpi.maven.useLocal = false
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = 'YEAR.+'
wpi.versions.wpimathVersion = 'YEAR.+
wpi.versions.wpimathVersion = 'YEAR.+'
```
The top of your ``build.gradle`` file should now look similar to the code below. Ignore any differences in versions.
@@ -89,15 +89,4 @@ wpi.versions.wpimathVersion = 'YEAR.424242.+'
# roboRIO Development
This repo contains a myRobot project built in way to do full project development without needing to do a full publish into GradleRIO. These also only require building the minimum amount of binaries for the roboRIO, so the builds are much faster as well.
The setup only works if the roboRIO is USB connected. If an alternate IP address is preferred, the `address` block in myRobot\build.gradle can be changed to point to another address.
The following 3 tasks can be used for deployment:
* `:myRobot:deployShared` deploys the C++ project using shared dependencies. Prefer this one for most C++ development.
* `:myRobot:deployStatic` deploys the C++ project with all dependencies statically linked.
* `:myRobot:deployJava` deploys the Java project and all required dependencies. Also installs the JRE if not currently installed.
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 `lvuser` account and run `frcRunRobot.sh` (It's in path).
See the [developerRobot](developerRobot/README.md) subproject.

View File

@@ -18,7 +18,7 @@ Desktop tools publish to the development repo on every push to main. To publish
## Publishing VS Code
Before publishing, make sure to update the gradlerio version in `vscode-wpilib/resources/gradle/version.txt` Also make sure the gradle wrapper version matches the wrapper required by gradlerio.
Upon pushing a tag, a release will be built, and the files will be uploaded to the releases on GitHub. For publishing to the marketplace, you need a Microsoft account and to be added as a maintainer.
Upon pushing a tag, a release will be built, and the files will be uploaded to the releases on GitHub.
## Publishing GradleRIO
Before publishing, make sure to update the version in build.gradle. Publishing must happen locally, using the command `./gradlew publishPlugin`. This does require your API key for publishing to be set.

View File

@@ -1,31 +1,41 @@
# WPILib CMake Support
WPILib is normally built with Gradle, however for some systems, such as Linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. Furthermore, the CMake build can be used for C++ development because it provides better build caching compared to Gradle. We provide the CMake build for these cases. Although it is supported on Windows, these docs will only go over Linux builds.
WPILib is normally built with Gradle, however for some systems, such as Linux based coprocessors, Gradle doesn't work correctly, especially if cscore is needed, which requires OpenCV. Furthermore, the CMake build can be used for C++ development because it provides better build caching compared to Gradle. We provide the CMake build for these cases. Although macOS is supported, these docs will only go over Linux and Windows builds, but should mostly work for macOS as well. If you are stuck, you can look at the GitHub workflows for any OS to see how it works.
## Libraries that get built
* wpiutil
* ntcore
* cscore
* apriltag
* cameraserver
* hal
* wpilib
* halsim
* cscore
* hal (simulation HAL only)
* ntcore
* romiVendordep
* simulation extensions
* wpigui
* wpimath
* wpiunits
* wpilib (wpilibc, wpilibj, and myRobot)
* wpilibNewCommands
* wpimath
* wpinet
* wpiunits
* wpiutil
* xrpVendordep
By default, all libraries except for the HAL and WPILib get built with a default CMake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java JARs.
## GUI apps that get built
* datalogtool
* glass
* outlineviewer
* roborioteamnumbersetter
* sysid
* halsim_gui (if simulation extensions are enabled)
By default, all libraries get built with a default CMake setup. The libraries are built as shared libraries, and include the JNI libraries as well as building the Java JARs. Data Log Tool and the roboRIO Team Number Setter are only built if libssh is available.
## Prerequisites
The jinja2 pip package is needed to generate classes for NT4's pubsub.
The protobuf library and compiler are needed for protobuf generation. The QuickBuffers protoc-gen package is also required when Java is being built; this can be obtained from https://github.com/HebiRobotics/QuickBuffers/releases/.
The protobuf library and compiler are needed for protobuf generation.
OpenCV needs to be findable by CMake. On systems like the Jetson, this is installed by default. Otherwise, you will need to build OpenCV from source and install it.
If you want JNI and Java, you will need a JDK of at least version 11 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
If you want JNI and Java, you will need a JDK of at least version 17 installed. In addition, you need a `JAVA_HOME` environment variable set properly and set to the JDK directory.
If you are building with unit tests or simulation modules, you will also need an Internet connection for the initial setup process, as CMake will clone google-test and imgui from GitHub.
@@ -33,34 +43,34 @@ If you are building with unit tests or simulation modules, you will also need an
The following build options are available:
* `WITH_JAVA` (ON Default)
* This option will enable Java and JNI builds. If this is on, `WITH_SHARED_LIBS` must be on. Otherwise CMake will error.
* `WITH_JAVA_SOURCE` (ON Default)
* This option will build Java source JARs for each enabled Java library. This does not require `WITH_JAVA` to be on, allowing source JARs to be built without the compiled JARs if desired.
* `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.
* `BUILD_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_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.
* This option will cause cscore to be built. Turning this off will implicitly disable cameraserver. If this is off, the OpenCV build requirement is removed.
* `WITH_EXAMPLES` (OFF Default)
* This option will build C++ examples.
* `WITH_GUI` (ON Default)
* This option will build GUI items. If this is off, and `WITH_SIMULATION_MODULES` is on, the simulation GUI will not be built.
* `WITH_JAVA` (ON Default)
* This option will enable Java and JNI builds. If this is on, `BUILD_SHARED_LIBS` must be on. Otherwise CMake will error.
* `WITH_JAVA_SOURCE` (`WITH_JAVA` Default)
* This option will build Java source JARs for each enabled Java library. This does not require `WITH_JAVA` to be on, allowing source JARs to be built without the compiled JARs if desired.
* `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.
* This option will cause ntcore to be built. Turning this off will implicitly disable wpinet, and will cause an error if `WITH_WPILIB` is enabled.
* `WITH_SIMULATION_MODULES` (ON Default)
* This option will build simulation modules.
* `WITH_TESTS` (ON Default)
* This option will build C++ unit tests. These can be run via `ctest -C <config>`, where `<config>` is the build configuration, e.g. `Debug` or `Release`.
* `WITH_WPILIB` (ON Default)
* This option will build the HAL and wpilibc/j during the build. The HAL is the simulation HAL, unless the external HAL options are used. The CMake build has no capability to build for the roboRIO.
* `WITH_WPIMATH` (ON Default)
* This option will build the wpimath library. This option must be on to build wpilib.
* `WITH_WPIUNITS` (ON Default)
* This option will build the wpiunits library. This option must be on to build the Java wpimath library and requires `WITH_JAVA` to also be on.
* `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)
* TODO
* This option will build wpilib with an externally built HAL.
* `EXTERNAL_HAL_FILE`
* TODO
* Set this option to the CMake File of the externally built HAL. NOTE: set it to the file itself, not the folder the file is located in!
* `OPENCV_JAVA_INSTALL_DIR`
* Set this option to the location of the archive of the OpenCV Java bindings (it should be called opencv-xxx.jar, with the x'es being version numbers). NOTE: set it to the LOCATION of the file, not the file itself!
* `NO_WERROR` (OFF Default)
@@ -78,24 +88,28 @@ cmake path/to/allwpilib/root
If you want to change any of the options, add `-DOPTIONHERE=VALUE` to the `cmake` command. This will check for any dependencies. If everything works properly this will succeed. If not, please check out the troubleshooting section for help.
If you want, you can also use `ccmake` in order to visually set these properties as well. [Here](https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html) is the link to the documentation for that program.
If you want, you can also use `ccmake` in order to visually set these properties as well. [Here](https://cmake.org/cmake/help/v3.0/manual/ccmake.1.html) is the link to the documentation for that program. On Windows, you can use `cmake-gui` instead.
Note that if you are cross-compiling, you will need to override the protobuf options manually to point to the libraries for the target platform. Leave the protoc binary location as the path to the binary for the host platform, since protoc needs to execute on the host platform.
## Building
Once you have cmake setup. run `make` from the directory you configured CMake in. This will build all libraries possible. If you have a multicore system, we recommend running `make` with multiple jobs. The usual rule of thumb is 1.5x the number of cores you have. To run a multiple job build, run the following command with x being the number of jobs you want.
Once you have CMake setup. run `cmake --build .` from the directory you configured CMake in. This will build all libraries possible. We recommend running `cmake --build .` with multiple jobs. For allwpilib, a good rule of thumb is one worker for every 2 GB of available RAM. To run a multiple job build, run the following command with x being the number of jobs you want.
```
make -jx
cmake --build . --parallel x
```
The `ninja` generator is also supported, and can be enabled by passing `-GNinja` to the initial `cmake` command.
Note: wpimath takes gigabytes of RAM to compile. Because of this, the compilers may crash while building due to a lack of memory and your computer may slow down. If you have less than 16 GB of RAM available, you may want to consider building it separately first by adding `--target wpimath` and running it with ~3 jobs to prevent crashes from running out of memory.
To build with a certain configuration, like `Debug` or `Release`, add `--config <config>`, where `<config>` is the name of the configuration you want to build with.
## Installing
After build, the easiest way to use the libraries is to install them. Run the following command to install the libraries. This will install them so that they can be used from external cmake projects.
After build, the easiest way to use the libraries is to install them. Run the following command to install the libraries. This will install them so that they can be used from external CMake projects.
```
sudo make install
sudo cmake --build . --target install
```
## Using the installed libraries for C++.
@@ -104,7 +118,7 @@ Using the libraries from C++ is the easiest way to use the built libraries.
To do so, create a new folder to contain your project. Add the following code below to a `CMakeLists.txt` file in that directory.
```
```cmake
cmake_minimum_required(VERSION 3.11)
project(vision_app) # Project Name Here
@@ -114,7 +128,7 @@ add_executable(my_vision_app main.cpp) # executable name as first parameter
target_link_libraries(my_vision_app cameraserver ntcore cscore wpiutil)
```
If you are using them, `wpilibc` and `hal` should be added before the `cameraserver` declaration in the `target_link_libraries` function.
If you want to use other libraries or are building a robot program, `wpilibc` and `hal` should be added in the `target_link_libraries` function, along with any other libraries you plan on using, e.g. `wpimath`.
Add a `main.cpp` file to contain your code, and create a build folder. Move into the build folder, and run
@@ -122,11 +136,43 @@ Add a `main.cpp` file to contain your code, and create a build folder. Move into
cmake /path/to/folder/containing/CMakeLists
```
After that, run `make`. That will create your executable. Then you should be able to run `./my_vision_app` to run your application.
After that, run `cmake --build .`. That will create your executable. Then you should be able to run `./my_vision_app` to run your application.
## Using the installed libraries for Java
TODO
Using the built JARs is move involved than using the C++ libraries, but mostly consists of adding the correct directories to PATH.
Add the directory where the JARs are located (e.g, `/usr/local/java`) to PATH. If you are on Windows, you also need to add the `lib`, `bin`, and `share` directories to PATH. Then, create a new folder to contain your project. Add the following code below to a `CMakeLists.txt` file in that directory.
```cmake
cmake_minimum_required(VERSION 3.11)
project(robot)
find_package(Java REQUIRED COMPONENTS Development)
include(UseJava)
find_package(wpilib REQUIRED)
find_jar(opencvJar opencv-xxx PATHS ENV PATH) # Change to OpenCV version
file(GLOB_RECURSE JAVA_SOURCES *.java)
# If you want Gradle compatibility or you are using one of the templates/examples, comment out the above line and uncomment this line instead:
# file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
add_jar(robot ${JAVA_SOURCES}
INCLUDE_JARS apriltag_jar cscore_jar hal_jar ntcore_jar wpilibNewCommands_jar wpimath_jar wpinet_jar wpiutil_jar wpiunits_jar wpilibj_jar ${opencvJar})
export_jars(TARGETS robot FILE robot.jar)
```
This includes all the built JARs except for the vendordeps. If you are not using a JAR/library, you may remove it.
Add a `Main.java` file to contain your code, and create a build folder. Move into the build folder, and run
```
cmake /path/to/folder/containing/CMakeLists
```
After that, run `cmake --build .` to create your JAR file. To execute the JAR file, you need to include the wpilib JARs and your JAR in the classpath, and execute your Java program's entry point. If you are using cscore or cameraserver, you also need to include the path to the OpenCV JAR. If you built it from source, it will be in your OpenCV build directory. If it's installed on the system, CMake may find it from PATH, but you will likely need to locate the JAR and manually give CMake the JAR directory. If you are on Linux, you will also need to add the path of the libraries to `LD_LIBRARY_PATH`. This can be done by prepending `LD_LIBRARY_PATH=/path/to/libraries` to the Java command. If you need to add more paths, separate them with colons. The final command should look like `java -cp "robot.jar:/path/to/library_jars/*" main.package.Main`, using a semicolon to separate paths instead of a colon if you are on Windows. If you are on Linux, the final command should look more like `LD_LIBRARY_PATH=/path/to/libraries java -cp robot.jar:/path/to/library_jars/* main.package.Main`.
## Using vendordeps
Vendordeps are not included as part of the `wpilib` CMake package. However, if you want to use a vendordep, you need to use `find_package(VENDORDEP)`, where `VENDORDEP` is the name of the vendordep (case-sensitive), like `xrpVendordep` or `romiVenderdep`. Note that wpilibNewCommands, while a vendordep in normal robot projects, is not built as a vendordep in CMake, and is instead included as part of the `wpilib` CMake package. After you used `find_package`, you can reference the vendordep library like normal, either by using `target_link_libraries` for C++ or `add_jar` for Java.
## Troubleshooting
Below are some common issues that are run into when building.
@@ -153,7 +199,7 @@ CMake Error at cscore/CMakeLists.txt:3 (find_package):
installed.
```
If you get that, you need make sure opencv was installed, and then reattempt to configure. If that doesn't work, set the `OpenCV_DIR` variable to the directory where you built OpenCV.
If you get that, you need make sure OpenCV was installed, and then reattempt to configure. If that doesn't work, set the `OpenCV_DIR` variable to the directory where you built OpenCV.
#### Missing Java
@@ -167,3 +213,18 @@ CMake Error at /usr/share/cmake-3.5/Modules/FindPackageHandleStandardArgs.cmake:
If this happens, make sure you have a JDK of at least version 8 installed, and that your JAVA_HOME variable is set properly to point to the JDK.
In addition, if you do not need Java, you can disable it with `-DWITH_JAVA=OFF`.
#### Java: Can't find dependent libraries
If one of the libraries can't be found, you will get an error similar to this one:
```
java.io.IOException: wpiHaljni could not be loaded from path or an embedded resource.
attempted to load for platform /windows/x86-64/
Last Load Error:
C:\Program Files (x86)\allwpilib\bin\wpiHaljni.dll: Can't find dependent libraries
```
If you get this error, that's usually an indication that not all your libraries are in your PATH. The two libraries that should be in your PATH are OpenCV and protobuf. If the error is coming from cscore, it's likely you're missing OpenCV. Otherwise, it's likely you're missing protobuf.
Note that Linux will not have this specific type of error, as it will usually tell you the dependent library you are missing. In that case, you most likely need to add the library to `LD_LIBRARY_PATH`.

View File

@@ -20,11 +20,11 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
- [Running examples in simulation](#running-examples-in-simulation)
- [Publishing](#publishing)
- [Structure and Organization](#structure-and-organization)
- [Contributing to WPILib](#contributing-to-wpilib)
- [Contributing to WPILib](./CONTRIBUTING.md)
## WPILib Mission
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.md).
The WPILib Mission is to enable FIRST Robotics teams to focus on writing game-specific software rather than focusing on hardware details - "raise the floor, don't lower the ceiling". We work to enable teams with limited programming knowledge and/or mentor experience to be as successful as possible, while not hampering the abilities of teams with more advanced programming capabilities. We support Kit of Parts control system components directly in the library. We also strive to keep parity between major features of each language (Java, C++, Python, and NI's LabVIEW), so that teams aren't at a disadvantage for choosing a specific programming language. WPILib is an open source project, licensed under the BSD 3-clause license. You can find a copy of the license [here](LICENSE.md).
# Quick Start
@@ -41,11 +41,11 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
## Requirements
- [JDK 11](https://adoptium.net/temurin/releases/?version=11)
- [JDK 17](https://adoptium.net/temurin/releases/?version=17)
- Note that the JRE is insufficient; the full JDK is required
- On Ubuntu, run `sudo apt install openjdk-11-jdk`
- On Windows, install the JDK 11 .msi from the link above
- On macOS, install the JDK 11 .pkg from the link above
- On Ubuntu, run `sudo apt install openjdk-17-jdk`
- On Windows, install the JDK 17 .msi from the link above
- On macOS, install the JDK 17 .pkg from the link above
- C++ compiler
- 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)
@@ -60,7 +60,7 @@ On macOS ARM, run `softwareupdate --install-rosetta`. This is necessary to be ab
## Setup
Clone the WPILib repository and follow the instructions above for installing any required tooling.
Clone the WPILib repository and follow the instructions above for installing any required tooling. The build process uses versioning information from git. Downloading the source is not sufficient to run the build.
See the [styleguide README](https://github.com/wpilibsuite/styleguide/blob/main/README.md) for wpiformat setup instructions.
@@ -179,7 +179,3 @@ The hal directory contains more C++ code meant to run on the roboRIO. HAL is an
The upstream_utils directory contains scripts for updating copies of thirdparty code in the repository.
The [styleguide repository](https://github.com/wpilibsuite/styleguide) contains our style guides for C++ and Java code. Anything submitted to the WPILib project needs to follow the code style guides outlined in there. For details about the style, please see the contributors document [here](CONTRIBUTING.md#coding-guidelines).
# Contributing to WPILib
See [CONTRIBUTING.md](CONTRIBUTING.md).

View File

@@ -43,6 +43,7 @@ Team 254 Library wpilibj/src/main/java/edu/wpi/first/wpilibj/spline/SplineP
Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
GCEM wpimath/src/main/native/thirdparty/gcem/include/
Sleipnir wpimath/src/main/native/thirdparty/sleipnir
==============================================================================
Google Test License
@@ -1209,3 +1210,18 @@ limitations under the License.
2024 Field Image
================
2024 Field Image from MikLast: https://www.chiefdelphi.com/t/2024-crescendo-top-down-field-renders/447764
================
Sleipnir License
================
Copyright (c) Sleipnir contributors
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 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.

View File

@@ -7,7 +7,7 @@ include(FetchContent)
fetchcontent_declare(
apriltaglib
GIT_REPOSITORY https://github.com/wpilibsuite/apriltag.git
GIT_TAG 64be6ab26abf5e995321997fd0752c609a7e30f4
GIT_TAG da208cc38c1b78fe89861616d44c0692e76b6b8b
)
# Don't use apriltag's CMakeLists.txt due to conflicting naming and JNI
@@ -30,7 +30,6 @@ 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)
@@ -43,6 +42,8 @@ if(WITH_JAVA)
${OPENCV_JAVA_INSTALL_DIR}
${OpenCV_INSTALL_PATH}/bin
${OpenCV_INSTALL_PATH}/share/java
${OpenCV_INSTALL_PATH}/share/java/opencv4
${OpenCV_INSTALL_PATH}/share/OpenCV/java
NO_DEFAULT_PATH
)
@@ -63,6 +64,7 @@ if(WITH_JAVA)
OUTPUT_NAME apriltag
GENERATE_NATIVE_HEADERS apriltag_jni_headers
)
set_property(TARGET apriltag_jar PROPERTY FOLDER "java")
install_jar(apriltag_jar DESTINATION ${java_lib_dest})
install_jar_exports(TARGETS apriltag_jar FILE apriltag_jar.cmake DESTINATION share/apriltag)
@@ -77,6 +79,7 @@ if(WITH_JAVA)
add_dependencies(apriltagjni apriltag_jar)
install(TARGETS apriltagjni EXPORT apriltagjni)
export(TARGETS apriltagjni FILE apriltagjni.cmake NAMESPACE apriltagjni::)
endif()
if(WITH_JAVA_SOURCE)
@@ -99,7 +102,13 @@ if(WITH_JAVA_SOURCE)
set_property(TARGET apriltag_src_jar PROPERTY FOLDER "java")
endif()
generate_resources(src/main/native/resources/edu/wpi/first/apriltag generated/main/cpp APRILTAG frc apriltag_resources_src)
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)
@@ -137,6 +146,7 @@ target_include_directories(
)
install(TARGETS apriltag EXPORT apriltag)
export(TARGETS apriltag FILE apriltag.cmake NAMESPACE apriltag::)
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/apriltag")
configure_file(apriltag-config.cmake.in ${WPILIB_BINARY_DIR}/apriltag-config.cmake)

View File

@@ -39,11 +39,7 @@ public class AprilTag {
@Override
public boolean equals(Object obj) {
if (obj instanceof AprilTag) {
var other = (AprilTag) obj;
return ID == other.ID && pose.equals(other.pose);
}
return false;
return obj instanceof AprilTag tag && ID == tag.ID && pose.equals(tag.pose);
}
@Override

View File

@@ -99,12 +99,8 @@ public class AprilTagDetector implements AutoCloseable {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Config)) {
return false;
}
Config other = (Config) obj;
return numThreads == other.numThreads
return obj instanceof Config other
&& numThreads == other.numThreads
&& quadDecimate == other.quadDecimate
&& quadSigma == other.quadSigma
&& refineEdges == other.refineEdges
@@ -194,12 +190,8 @@ public class AprilTagDetector implements AutoCloseable {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof QuadThresholdParameters)) {
return false;
}
QuadThresholdParameters other = (QuadThresholdParameters) obj;
return minClusterPixels == other.minClusterPixels
return obj instanceof QuadThresholdParameters other
&& minClusterPixels == other.minClusterPixels
&& maxNumMaxima == other.maxNumMaxima
&& criticalAngle == other.criticalAngle
&& maxLineFitMSE == other.maxLineFitMSE

View File

@@ -148,19 +148,14 @@ public class AprilTagFieldLayout {
*/
@JsonIgnore
public final 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");
}
var pose =
switch (origin) {
case kBlueAllianceWallRightSide -> Pose3d.kZero;
case kRedAllianceWallRightSide -> new Pose3d(
new Translation3d(m_fieldDimensions.fieldLength, m_fieldDimensions.fieldWidth, 0),
new Rotation3d(0, 0, Math.PI));
};
setOrigin(pose);
}
/**
@@ -230,12 +225,19 @@ public class AprilTagFieldLayout {
* @throws UncheckedIOException If the layout does not exist.
*/
public static AprilTagFieldLayout loadField(AprilTagFields field) {
try {
return loadFromResource(field.m_resourceFile);
} catch (IOException e) {
throw new UncheckedIOException(
"Could not load AprilTagFieldLayout from " + field.m_resourceFile, e);
if (field.m_fieldLayout == null) {
try {
field.m_fieldLayout = loadFromResource(field.m_resourceFile);
} catch (IOException e) {
throw new UncheckedIOException(
"Could not load AprilTagFieldLayout from " + field.m_resourceFile, e);
}
}
// Copy layout because the layout's origin is mutable
return new AprilTagFieldLayout(
field.m_fieldLayout.getTags(),
field.m_fieldLayout.getFieldLength(),
field.m_fieldLayout.getFieldWidth());
}
/**
@@ -264,11 +266,9 @@ public class AprilTagFieldLayout {
@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;
return obj instanceof AprilTagFieldLayout layout
&& m_apriltags.equals(layout.m_apriltags)
&& m_origin.equals(layout.m_origin);
}
@Override
@@ -281,11 +281,11 @@ public class AprilTagFieldLayout {
private static class FieldDimensions {
@SuppressWarnings("MemberName")
@JsonProperty(value = "length")
public double fieldLength;
public final double fieldLength;
@SuppressWarnings("MemberName")
@JsonProperty(value = "width")
public double fieldWidth;
public final double fieldWidth;
@JsonCreator()
FieldDimensions(

View File

@@ -24,6 +24,8 @@ public enum AprilTagFields {
/** Resource filename. */
public final String m_resourceFile;
AprilTagFieldLayout m_fieldLayout;
AprilTagFields(String resourceFile) {
m_resourceFile = kBaseResourceDir + resourceFile;
}
@@ -33,7 +35,9 @@ public enum AprilTagFields {
*
* @return AprilTagFieldLayout of the field
* @throws UncheckedIOException If the layout does not exist
* @deprecated Use {@link AprilTagFieldLayout#loadField(AprilTagFields)} instead.
*/
@Deprecated(forRemoval = true, since = "2025")
public AprilTagFieldLayout loadAprilTagLayoutField() {
return AprilTagFieldLayout.loadField(this);
}

View File

@@ -55,12 +55,8 @@ public class AprilTagPoseEstimator {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Config)) {
return false;
}
Config other = (Config) obj;
return tagSize == other.tagSize
return obj instanceof Config other
&& tagSize == other.tagSize
&& fx == other.fx
&& fy == other.fy
&& cx == other.cx

View File

@@ -17,8 +17,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class AprilTagJNI {
static boolean libraryLoaded = false;
static RuntimeLoader<AprilTagJNI> loader = null;
/** Sets whether JNI should be loaded in the static block. */
public static class Helper {
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
@@ -48,10 +46,7 @@ public class AprilTagJNI {
static {
if (Helper.getExtractOnStaticLoad()) {
try {
loader =
new RuntimeLoader<>(
"apriltagjni", RuntimeLoader.getDefaultExtractionRoot(), AprilTagJNI.class);
loader.loadLibrary();
RuntimeLoader.loadLibrary("apriltagjni");
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);

View File

@@ -166,7 +166,9 @@ void from_json(const wpi::json& json, AprilTagFieldLayout& layout);
*
* @param field The predefined field
* @return AprilTagFieldLayout of the field
* @deprecated Use AprilTagFieldLayout::LoadField() instead
*/
[[deprecated("Use AprilTagFieldLayout::LoadField() instead")]]
WPILIB_DLLEXPORT AprilTagFieldLayout
LoadAprilTagLayoutField(AprilTagField field);

View File

@@ -29,15 +29,10 @@ class AprilTagDetectorTest {
@SuppressWarnings("MemberName")
AprilTagDetector detector;
static RuntimeLoader<Core> loader;
@BeforeAll
static void beforeAll() {
try {
loader =
new RuntimeLoader<>(
Core.NATIVE_LIBRARY_NAME, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
loader.loadLibrary();
RuntimeLoader.loadLibrary(Core.NATIVE_LIBRARY_NAME);
} catch (IOException ex) {
fail(ex);
}
@@ -158,7 +153,7 @@ class AprilTagDetectorTest {
var estimator =
new AprilTagPoseEstimator(new AprilTagPoseEstimator.Config(0.2, 500, 500, 320, 240));
AprilTagPoseEstimate est = estimator.estimateOrthogonalIteration(results[0], 200);
assertEquals(new Transform3d(), est.pose2);
assertEquals(Transform3d.kZero, est.pose2);
Transform3d pose = estimator.estimate(results[0]);
assertEquals(est.pose1, pose);
} finally {

View File

@@ -19,7 +19,7 @@ class AprilTagPoseSetOriginTest {
var layout =
new AprilTagFieldLayout(
List.of(
new AprilTag(1, new Pose3d(new Translation3d(0, 0, 0), new Rotation3d(0, 0, 0))),
new AprilTag(1, Pose3d.kZero),
new AprilTag(
2,
new Pose3d(
@@ -40,7 +40,7 @@ class AprilTagPoseSetOriginTest {
new Pose3d(
new Translation3d(
Units.feetToMeters(50.0), Units.feetToMeters(23.0), Units.feetToMeters(4)),
new Rotation3d(0.0, 0.0, 0)),
Rotation3d.kZero),
layout.getTagPose(2).orElse(null));
}
}

View File

@@ -20,8 +20,8 @@ class AprilTagSerializationTest {
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)))),
new AprilTag(1, Pose3d.kZero),
new AprilTag(3, new Pose3d(0, 1, 0, Rotation3d.kZero))),
Units.feetToMeters(54.0),
Units.feetToMeters(27.0));

View File

@@ -37,7 +37,7 @@ class LoadConfigTest {
Units.inchesToMeters(127.272),
Units.inchesToMeters(216.01),
Units.inchesToMeters(67.932),
new Rotation3d(0, 0, 0));
Rotation3d.kZero);
Optional<Pose3d> maybePose = layout.getTagPose(1);
assertTrue(maybePose.isPresent());
assertEquals(expectedPose, maybePose.get());

View File

@@ -114,8 +114,8 @@ subprojects {
plugins.withType(JavaPlugin) {
java {
sourceCompatibility = 11
targetCompatibility = 11
sourceCompatibility = 17
targetCompatibility = 17
}
}

View File

@@ -9,5 +9,5 @@ repositories {
}
}
dependencies {
implementation "edu.wpi.first:native-utils:2024.5.2"
implementation "edu.wpi.first:native-utils:2025.1.0"
}

View File

@@ -9,16 +9,19 @@ find_package(OpenCV REQUIRED)
if(WITH_JAVA)
find_package(Java REQUIRED)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
#find java files, copy them locally
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/java/opencv4)
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
PATHS
${OPENCV_JAVA_INSTALL_DIR}
${OpenCV_INSTALL_PATH}/bin
${OpenCV_INSTALL_PATH}/share/java
${OpenCV_INSTALL_PATH}/share/OpenCV/java
NO_DEFAULT_PATH
)
@@ -30,6 +33,7 @@ if(WITH_JAVA)
INCLUDE_JARS wpiutil_jar cscore_jar ntcore_jar ${OPENCV_JAR_FILE}
OUTPUT_NAME cameraserver
)
set_property(TARGET cameraserver_jar PROPERTY FOLDER "java")
install_jar(cameraserver_jar DESTINATION ${java_lib_dest})
install_jar_exports(
@@ -73,6 +77,7 @@ target_link_libraries(cameraserver PUBLIC ntcore cscore wpiutil ${OpenCV_LIBS})
set_property(TARGET cameraserver PROPERTY FOLDER "libraries")
install(TARGETS cameraserver EXPORT cameraserver)
export(TARGETS cameraserver FILE cameraserver.cmake NAMESPACE cameraserver::)
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cameraserver")
configure_file(cameraserver-config.cmake.in ${WPILIB_BINARY_DIR}/cameraserver-config.cmake)

View File

@@ -14,6 +14,7 @@
#include <wpi/StringExtras.h>
#include <wpi/fmt/raw_ostream.h>
#include <wpi/json.h>
#include <wpi/print.h>
#include <wpi/raw_ostream.h>
#include "cameraserver/CameraServer.h"
@@ -71,7 +72,7 @@ bool ReadCameraConfig(const wpi::json& config) {
try {
c.name = config.at("name").get<std::string>();
} catch (const wpi::json::exception& e) {
fmt::print(stderr, "config error in '{}': could not read camera name: {}\n",
wpi::print(stderr, "config error in '{}': could not read camera name: {}\n",
configFile, e.what());
return false;
}
@@ -80,7 +81,7 @@ bool ReadCameraConfig(const wpi::json& config) {
try {
c.path = config.at("path").get<std::string>();
} catch (const wpi::json::exception& e) {
fmt::print(stderr,
wpi::print(stderr,
"config error in '{}': camera '{}': could not read path: {}\n",
configFile, c.name, e.what());
return false;
@@ -98,7 +99,7 @@ bool ReadConfig() {
std::unique_ptr<wpi::MemoryBuffer> fileBuffer =
wpi::MemoryBuffer::GetFile(configFile, ec);
if (fileBuffer == nullptr || ec) {
fmt::print(stderr, "could not open '{}': {}\n", configFile, ec.message());
wpi::print(stderr, "could not open '{}': {}\n", configFile, ec.message());
return false;
}
@@ -107,14 +108,14 @@ bool ReadConfig() {
try {
j = wpi::json::parse(fileBuffer->GetCharBuffer());
} catch (const wpi::json::parse_error& e) {
fmt::print(stderr, "config error in '{}': byte {}: {}\n", configFile,
wpi::print(stderr, "config error in '{}': byte {}: {}\n", configFile,
e.byte, e.what());
return false;
}
// top level must be an object
if (!j.is_object()) {
fmt::print(stderr, "config error in '{}': must be JSON object\n",
wpi::print(stderr, "config error in '{}': must be JSON object\n",
configFile);
return false;
}
@@ -123,7 +124,7 @@ bool ReadConfig() {
try {
team = j.at("team").get<unsigned int>();
} catch (const wpi::json::exception& e) {
fmt::print(stderr, "config error in '{}': could not read team number: {}\n",
wpi::print(stderr, "config error in '{}': could not read team number: {}\n",
configFile, e.what());
return false;
}
@@ -137,13 +138,13 @@ bool ReadConfig() {
} else if (wpi::equals_lower(str, "server")) {
server = true;
} else {
fmt::print(
wpi::print(
stderr,
"config error in '{}': could not understand ntmode value '{}'\n",
configFile, str);
}
} catch (const wpi::json::exception& e) {
fmt::print(stderr, "config error in '{}': could not read ntmode: {}\n",
wpi::print(stderr, "config error in '{}': could not read ntmode: {}\n",
configFile, e.what());
}
}
@@ -156,7 +157,7 @@ bool ReadConfig() {
}
}
} catch (const wpi::json::exception& e) {
fmt::print(stderr, "config error in '{}': could not read cameras: {}\n",
wpi::print(stderr, "config error in '{}': could not read cameras: {}\n",
configFile, e.what());
return false;
}
@@ -165,7 +166,7 @@ bool ReadConfig() {
}
void StartCamera(const CameraConfig& config) {
fmt::print("Starting camera '{}' on {}\n", config.name, config.path);
wpi::print("Starting camera '{}' on {}\n", config.name, config.path);
auto camera =
frc::CameraServer::StartAutomaticCapture(config.name, config.path);
@@ -189,7 +190,7 @@ int main(int argc, char* argv[]) {
std::puts("Setting up NetworkTables server");
ntinst.StartServer();
} else {
fmt::print("Setting up NetworkTables client for team {}\n", team);
wpi::print("Setting up NetworkTables client for team {}\n", team);
ntinst.StartClient4("multicameraserver");
ntinst.SetServerTeam(team);
}

View File

@@ -94,24 +94,24 @@ public final class CameraServer {
void update(VideoEvent event) {
switch (event.propertyKind) {
case kBoolean:
case kBoolean -> {
if (m_booleanValueEntry != null) {
m_booleanValueEntry.set(event.value != 0);
}
break;
case kInteger:
case kEnum:
}
case kInteger, kEnum -> {
if (m_integerValueEntry != null) {
m_integerValueEntry.set(event.value);
}
break;
case kString:
}
case kString -> {
if (m_stringValueEntry != null) {
m_stringValueEntry.set(event.valueStr);
}
break;
default:
break;
}
default -> {
// NOP
}
}
}
@@ -231,112 +231,93 @@ public final class CameraServer {
event -> {
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;
case kSourceCreated -> {
// Create subtable for the camera
NetworkTable table = m_publishTable.getSubTable(event.name);
m_publishers.put(
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
}
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)
}
}
case kSourceDestroyed:
{
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
if (publisher != null) {
}
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);
}
}
case kSourceDisconnected -> {
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_connectedPublisher.set(false);
}
}
case kSourceVideoModesUpdated -> {
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
}
}
case kSourceVideoModeChanged -> {
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_modeEntry.set(videoModeToString(event.mode));
}
}
case kSourcePropertyCreated -> {
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
if (publisher != null) {
publisher.m_properties.put(
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
}
}
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);
}
}
}
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 {
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)
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,
kSinkCreated,
kSinkDestroyed,
kNetworkInterfacesChanged -> {
m_addresses = CameraServerJNI.getNetworkInterfaces();
updateStreamValues();
}
default -> {
// NOP
}
}
}
},
@@ -352,23 +333,20 @@ public final class CameraServer {
* @param source Source index.
*/
private static String makeSourceValue(int source) {
switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) {
case kUsb:
return "usb:" + CameraServerJNI.getUsbCameraPath(source);
case kHttp:
{
String[] urls = CameraServerJNI.getHttpCameraUrls(source);
if (urls.length > 0) {
return "ip:" + urls[0];
} else {
return "ip:";
}
return switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) {
case kUsb -> "usb:" + CameraServerJNI.getUsbCameraPath(source);
case kHttp -> {
String[] urls = CameraServerJNI.getHttpCameraUrls(source);
if (urls.length > 0) {
yield "ip:" + urls[0];
} else {
yield "ip:";
}
case kCv:
return "cv:";
default:
return "unknown:";
}
}
case kCv -> "cv:";
case kRaw -> "raw:";
case kUnknown -> "unknown:";
};
}
/**
@@ -502,20 +480,17 @@ public final class CameraServer {
/** Provide string description of pixel format. */
private static String pixelFormatToString(PixelFormat pixelFormat) {
switch (pixelFormat) {
case kMJPEG:
return "MJPEG";
case kYUYV:
return "YUYV";
case kRGB565:
return "RGB565";
case kBGR:
return "BGR";
case kGray:
return "Gray";
default:
return "Unknown";
}
return switch (pixelFormat) {
case kMJPEG -> "MJPEG";
case kYUYV -> "YUYV";
case kRGB565 -> "RGB565";
case kBGR -> "BGR";
case kBGRA -> "BGRA";
case kGray -> "Gray";
case kY16 -> "Y16";
case kUYVY -> "UYVY";
case kUnknown -> "Unknown";
};
}
/**
@@ -632,7 +607,10 @@ public final class CameraServer {
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String host) {
return addAxisCamera("Axis Camera", host);
}
@@ -644,7 +622,10 @@ public final class CameraServer {
*
* @param hosts Array of Camera host IPs/DNS names
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String[] hosts) {
return addAxisCamera("Axis Camera", hosts);
}
@@ -655,7 +636,10 @@ public final class CameraServer {
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String name, String host) {
AxisCamera camera = new AxisCamera(name, host);
// Create a passthrough MJPEG server for USB access
@@ -670,7 +654,10 @@ public final class CameraServer {
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @return The Axis camera capturing images.
* @deprecated Call startAutomaticCapture with a HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
@SuppressWarnings("removal")
public static AxisCamera addAxisCamera(String name, String[] hosts) {
AxisCamera camera = new AxisCamera(name, hosts);
// Create a passthrough MJPEG server for USB access

View File

@@ -113,6 +113,8 @@ static std::string_view MakeSourceValue(CS_Source source,
}
case CS_SOURCE_CV:
return "cv:";
case CS_SOURCE_RAW:
return "raw:";
default:
return "unknown:";
}
@@ -502,6 +504,7 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(std::string_view name,
return camera;
}
WPI_IGNORE_DEPRECATED
cs::AxisCamera CameraServer::AddAxisCamera(std::string_view host) {
return AddAxisCamera("Axis Camera", host);
}
@@ -557,7 +560,7 @@ cs::AxisCamera CameraServer::AddAxisCamera(std::string_view name,
csShared->ReportAxisCamera(camera.GetHandle());
return camera;
}
WPI_UNIGNORE_DEPRECATED
cs::MjpegServer CameraServer::AddSwitchedCamera(std::string_view name) {
auto& inst = ::GetInstance();
// create a dummy CvSource

View File

@@ -10,6 +10,8 @@
#include <string>
#include <string_view>
#include <wpi/deprecated.h>
#include "cscore.h"
#include "cscore_cv.h"
@@ -73,13 +75,16 @@ class CameraServer {
*/
static cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
WPI_IGNORE_DEPRECATED
/**
* Adds an Axis IP camera.
*
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view host);
/**
@@ -88,7 +93,9 @@ class CameraServer {
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(const char* host);
/**
@@ -97,7 +104,9 @@ class CameraServer {
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(const std::string& host);
/**
@@ -106,7 +115,9 @@ class CameraServer {
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::span<const std::string> hosts);
/**
@@ -115,8 +126,10 @@ class CameraServer {
* This overload calls AddAxisCamera() with name "Axis Camera".
*
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts);
/**
@@ -124,7 +137,9 @@ class CameraServer {
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::string_view host);
@@ -133,7 +148,9 @@ class CameraServer {
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name, const char* host);
/**
@@ -141,7 +158,9 @@ class CameraServer {
*
* @param name The name to give the camera
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
const std::string& host);
@@ -150,7 +169,9 @@ class CameraServer {
*
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::span<const std::string> hosts);
@@ -159,10 +180,13 @@ class CameraServer {
*
* @param name The name to give the camera
* @param hosts Array of Camera host IPs/DNS names
* @deprecated Call StartAutomaticCapture with a HttpCamera instead.
*/
template <typename T>
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
static cs::AxisCamera AddAxisCamera(std::string_view name,
std::initializer_list<T> hosts);
WPI_UNIGNORE_DEPRECATED
/**
* Adds a virtual camera for switching between two streams. Unlike the

View File

@@ -11,6 +11,7 @@
namespace frc {
WPI_IGNORE_DEPRECATED
template <typename T>
inline cs::AxisCamera CameraServer::AddAxisCamera(
std::initializer_list<T> hosts) {
@@ -27,5 +28,6 @@ inline cs::AxisCamera CameraServer::AddAxisCamera(
}
return AddAxisCamera(name, vec);
}
WPI_UNIGNORE_DEPRECATED
} // namespace frc

View File

@@ -3,6 +3,7 @@ include(CompileWarnings)
macro(wpilib_add_test name srcdir)
file(GLOB_RECURSE test_src ${srcdir}/*.cpp)
add_executable(${name}_test ${test_src})
set_property(TARGET ${name}_test PROPERTY FOLDER "tests")
wpilib_target_warnings(${name}_test)
if(BUILD_SHARED_LIBS)
target_compile_definitions(${name}_test PRIVATE -DGTEST_LINKED_AS_SHARED_LIBRARY)

View File

@@ -8,6 +8,7 @@
#include <fmt/core.h>
#include <gtest/gtest.h>
#include <hal/HAL.h>
#include <wpi/print.h>
#include "mockds/MockDS.h"
@@ -27,7 +28,7 @@ class TestEnvironment : public testing::Environment {
m_alreadySetUp = true;
if (!HAL_Initialize(500, 0)) {
fmt::print(stderr, "FATAL ERROR: HAL could not be initialized\n");
wpi::print(stderr, "FATAL ERROR: HAL could not be initialized\n");
std::exit(-1);
}
@@ -39,7 +40,7 @@ class TestEnvironment : public testing::Environment {
// able to run on the hardware.
HAL_ObserveUserProgramStarting();
fmt::print("Started coms\n");
wpi::print("Started coms\n");
int enableCounter = 0;
@@ -54,13 +55,13 @@ class TestEnvironment : public testing::Environment {
if (enableCounter > 50) {
// Robot did not enable properly after 5 seconds.
// Force exit
fmt::print(stderr, " Failed to enable. Aborting\n");
wpi::print(stderr, " Failed to enable. Aborting\n");
std::terminate();
}
std::this_thread::sleep_for(100ms);
fmt::print("Waiting for enable: {}\n", enableCounter++);
wpi::print("Waiting for enable: {}\n", enableCounter++);
HAL_RefreshDSData();
}
std::this_thread::sleep_for(500ms);

View File

@@ -12,12 +12,13 @@
#include <hal/cpp/fpga_clock.h>
#include <wpi/Logger.h>
#include <wpi/SmallVector.h>
#include <wpi/print.h>
#include <wpinet/UDPClient.h>
static void LoggerFunc(unsigned int level, const char* file, unsigned int line,
const char* msg) {
if (level == 20) {
fmt::print(stderr, "DS: {}\n", msg);
wpi::print(stderr, "DS: {}\n", msg);
return;
}
@@ -31,7 +32,7 @@ static void LoggerFunc(unsigned int level, const char* file, unsigned int line,
} else {
return;
}
fmt::print(stderr, "DS: {}: {} ({}:{})\n", levelmsg, msg, file, line);
wpi::print(stderr, "DS: {}: {} ({}:{})\n", levelmsg, msg, file, line);
}
static void generateEnabledDsPacket(wpi::SmallVectorImpl<uint8_t>& data,

View File

@@ -190,12 +190,12 @@ struct RelayHandle {
HAL_RelayHandle handle = 0;
};
#define ASSERT_LAST_ERROR_STATUS(status, x) \
do { \
ASSERT_EQ(status, HAL_USE_LAST_ERROR); \
[[maybe_unused]] const char* lastErrorMessageInMacro = \
HAL_GetLastError(&status); \
ASSERT_EQ(status, x); \
#define ASSERT_LAST_ERROR_STATUS(status, x) \
do { \
ASSERT_EQ(status, HAL_USE_LAST_ERROR); \
[[maybe_unused]] \
const char* lastErrorMessageInMacro = HAL_GetLastError(&status); \
ASSERT_EQ(status, x); \
} while (0)
} // namespace hlt

View File

@@ -48,6 +48,7 @@ target_link_libraries(cscore PUBLIC wpinet wpiutil ${OpenCV_LIBS})
set_property(TARGET cscore PROPERTY FOLDER "libraries")
install(TARGETS cscore EXPORT cscore)
export(TARGETS cscore FILE cscore.cmake NAMESPACE cscore::)
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
configure_file(cscore-config.cmake.in ${WPILIB_BINARY_DIR}/cscore-config.cmake)
@@ -83,12 +84,11 @@ if(WITH_JAVA)
find_package(Java REQUIRED)
find_package(JNI REQUIRED)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
#find java files, copy them locally
if("${OPENCV_JAVA_INSTALL_DIR}" STREQUAL "")
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/OpenCV/java/)
set(OPENCV_JAVA_INSTALL_DIR ${OpenCV_INSTALL_PATH}/share/java/opencv4)
endif()
find_file(
@@ -98,6 +98,7 @@ if(WITH_JAVA)
${OPENCV_JAVA_INSTALL_DIR}
${OpenCV_INSTALL_PATH}/bin
${OpenCV_INSTALL_PATH}/share/java
${OpenCV_INSTALL_PATH}/share/OpenCV/java
NO_DEFAULT_PATH
)
find_file(
@@ -112,7 +113,10 @@ if(WITH_JAVA)
${OpenCV_INSTALL_PATH}/bin/Release
${OpenCV_INSTALL_PATH}/bin/Debug
${OpenCV_INSTALL_PATH}/lib
${OpenCV_INSTALL_PATH}/lib/Release
${OpenCV_INSTALL_PATH}/lib/Debug
${OpenCV_INSTALL_PATH}/lib/jni
${OpenCV_INSTALL_PATH}/share/java/opencv4
NO_DEFAULT_PATH
)
@@ -128,6 +132,7 @@ if(WITH_JAVA)
OUTPUT_NAME cscore
GENERATE_NATIVE_HEADERS cscore_jni_headers
)
set_property(TARGET cscore_jar PROPERTY FOLDER "java")
install_jar(cscore_jar DESTINATION ${java_lib_dest})
install_jar_exports(TARGETS cscore_jar FILE cscore_jar.cmake DESTINATION share/cscore)
@@ -141,6 +146,7 @@ if(WITH_JAVA)
${cvFile}Loc
NAMES
${cvFile}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}.dll
${cvFile}${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}d.dll
PATHS
${OPENCV_JAVA_INSTALL_DIR}
${OpenCV_INSTALL_PATH}/bin
@@ -163,6 +169,7 @@ if(WITH_JAVA)
add_dependencies(cscorejni cscore_jar)
install(TARGETS cscorejni EXPORT cscorejni)
export(TARGETS cscorejni FILE cscorejni.cmake NAMESPACE cscorejni::)
endif()
if(WITH_JAVA_SOURCE)

View File

@@ -4,7 +4,7 @@
#include <cstdio>
#include <fmt/format.h>
#include <wpi/print.h>
#include "cscore.h"
@@ -12,11 +12,11 @@ int main() {
CS_Status status = 0;
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
fmt::print("{}: {} ({})\n", caminfo.dev, caminfo.path, caminfo.name);
wpi::print("{}: {} ({})\n", caminfo.dev, caminfo.path, caminfo.name);
if (!caminfo.otherPaths.empty()) {
std::puts("Other device paths:");
for (auto&& path : caminfo.otherPaths) {
fmt::print(" {}\n", path);
wpi::print(" {}\n", path);
}
}
@@ -24,26 +24,26 @@ int main() {
std::puts("Properties:");
for (const auto& prop : camera.EnumerateProperties()) {
fmt::print(" {}", prop.GetName());
wpi::print(" {}", prop.GetName());
switch (prop.GetKind()) {
case cs::VideoProperty::kBoolean:
fmt::print(" (bool): value={} default={}", prop.Get(),
wpi::print(" (bool): value={} default={}", prop.Get(),
prop.GetDefault());
break;
case cs::VideoProperty::kInteger:
fmt::print(" (int): value={} min={} max={} step={} default={}",
wpi::print(" (int): value={} min={} max={} step={} default={}",
prop.Get(), prop.GetMin(), prop.GetMax(), prop.GetStep(),
prop.GetDefault());
break;
case cs::VideoProperty::kString:
fmt::print(" (string): {}", prop.GetString());
wpi::print(" (string): {}", prop.GetString());
break;
case cs::VideoProperty::kEnum: {
fmt::print(" (enum): value={}", prop.Get());
wpi::print(" (enum): value={}", prop.Get());
auto choices = prop.GetChoices();
for (size_t i = 0; i < choices.size(); ++i) {
if (!choices[i].empty()) {
fmt::print("\n {}: {}", i, choices[i]);
wpi::print("\n {}: {}", i, choices[i]);
}
}
break;
@@ -71,7 +71,7 @@ int main() {
pixelFormat = "Unknown";
break;
}
fmt::print(" PixelFormat:{} Width:{} Height:{} FPS:{}\n", pixelFormat,
wpi::print(" PixelFormat:{} Width:{} Height:{} FPS:{}\n", pixelFormat,
mode.width, mode.height, mode.fps);
}
}

View File

@@ -4,8 +4,8 @@
#include <cstdio>
#include <fmt/core.h>
#include <opencv2/core/core.hpp>
#include <wpi/print.h>
#include "cscore.h"
#include "cscore_cv.h"
@@ -24,10 +24,10 @@ int main() {
for (;;) {
uint64_t time = cvsink.GrabFrame(test);
if (time == 0) {
fmt::print("error: {}\n", cvsink.GetError());
wpi::print("error: {}\n", cvsink.GetError());
continue;
}
fmt::print("got frame at time {} size ({}, {})\n", time, test.size().width,
wpi::print("got frame at time {} size ({}, {})\n", time, test.size().width,
test.size().height);
cv::flip(test, flip, 0);
cvsource.PutFrame(flip);

View File

@@ -6,8 +6,8 @@
#include <cstdio>
#include <thread>
#include <fmt/format.h>
#include <wpi/StringExtras.h>
#include <wpi/print.h>
#include "cscore.h"
@@ -73,26 +73,26 @@ int main(int argc, char** argv) {
// Print settings
std::puts("Properties:");
for (const auto& prop : camera.EnumerateProperties()) {
fmt::print(" {}", prop.GetName());
wpi::print(" {}", prop.GetName());
switch (prop.GetKind()) {
case cs::VideoProperty::kBoolean:
fmt::print(" (bool): value={} default={}", prop.Get(),
wpi::print(" (bool): value={} default={}", prop.Get(),
prop.GetDefault());
break;
case cs::VideoProperty::kInteger:
fmt::print(" (int): value={} min={} max={} step={} default={}",
wpi::print(" (int): value={} min={} max={} step={} default={}",
prop.Get(), prop.GetMin(), prop.GetMax(), prop.GetStep(),
prop.GetDefault());
break;
case cs::VideoProperty::kString:
fmt::print(" (string): {}", prop.GetString());
wpi::print(" (string): {}", prop.GetString());
break;
case cs::VideoProperty::kEnum: {
fmt::print(" (enum): value={}", prop.Get());
wpi::print(" (enum): value={}", prop.Get());
auto choices = prop.GetChoices();
for (size_t i = 0; i < choices.size(); ++i) {
if (!choices[i].empty()) {
fmt::print("\n {}: {}", i, choices[i]);
wpi::print("\n {}: {}", i, choices[i]);
}
}
break;

View File

@@ -2,8 +2,8 @@
// 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 <fmt/core.h>
#include <opencv2/core/core.hpp>
#include <wpi/print.h>
#include "cscore.h"
#include "cscore_cv.h"
@@ -24,10 +24,10 @@ int main() {
for (;;) {
uint64_t time = cvsink.GrabFrame(test);
if (time == 0) {
fmt::print("error: {}\n", cvsink.GetError());
wpi::print("error: {}\n", cvsink.GetError());
continue;
}
fmt::print("got frame at time {} size ({}, {})\n", time, test.size().width,
wpi::print("got frame at time {} size ({}, {})\n", time, test.size().width,
test.size().height);
cv::flip(test, flip, 0);
cvsource.PutFrame(flip);

View File

@@ -4,15 +4,15 @@
#include <cstdio>
#include <fmt/format.h>
#include <wpi/print.h>
#include "cscore.h"
int main() {
fmt::print("hostname: {}\n", cs::GetHostname());
wpi::print("hostname: {}\n", cs::GetHostname());
std::puts("IPv4 network addresses:");
for (const auto& addr : cs::GetNetworkInterfaces()) {
fmt::print(" {}\n", addr);
wpi::print(" {}\n", addr);
}
cs::UsbCamera camera{"usbcam", 0};
camera.SetVideoMode(cs::VideoMode::kMJPEG, 320, 240, 30);
@@ -22,7 +22,7 @@ int main() {
CS_Status status = 0;
cs::AddListener(
[&](const cs::RawEvent& event) {
fmt::print("FPS={} MBPS={}\n", camera.GetActualFPS(),
wpi::print("FPS={} MBPS={}\n", camera.GetActualFPS(),
(camera.GetActualDataRate() / 1000000.0));
},
cs::RawEvent::kTelemetryUpdated, false, &status);

View File

@@ -6,13 +6,12 @@
#include <thread>
#include <vector>
#include <fmt/format.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <wpi/print.h>
#include <wpi/spinlock.h>
#include <wpigui.h>
@@ -39,7 +38,7 @@ int main() {
// get frame from camera
uint64_t time = cvsink.GrabFrame(frame);
if (time == 0) {
fmt::print("error: {}\n", cvsink.GetError());
wpi::print("error: {}\n", cvsink.GetError());
continue;
}

View File

@@ -20,22 +20,12 @@ public class RawCVMatSink extends ImageSink {
int bgrValue = PixelFormat.kBGR.getValue();
private int getCVFormat(PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case kYUYV:
case kRGB565:
type = CvType.CV_8UC2;
break;
case kBGR:
type = CvType.CV_8UC3;
break;
case kGray:
case kMJPEG:
default:
type = CvType.CV_8UC1;
break;
}
return type;
return switch (pixelFormat) {
case kYUYV, kRGB565, kY16, kUYVY -> CvType.CV_8UC2;
case kBGR -> CvType.CV_8UC3;
case kBGRA -> CvType.CV_8UC4;
case kGray, kMJPEG, kUnknown -> CvType.CV_8UC1;
};
}
/**

View File

@@ -4,13 +4,13 @@
package edu.wpi.first.cscore;
import edu.wpi.first.util.RuntimeDetector;
import edu.wpi.first.util.CombinedRuntimeLoader;
public final class DevMain {
/** Main method. */
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println(RuntimeDetector.getPlatformPath());
System.out.println(CombinedRuntimeLoader.getPlatformPath());
System.out.println(CameraServerJNI.getHostname());
}

View File

@@ -2,10 +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.
#include <fmt/core.h>
#include <wpi/print.h>
#include "cscore.h"
int main() {
fmt::print("{}\n", cs::GetHostname());
wpi::print("{}\n", cs::GetHostname());
}

View File

@@ -4,7 +4,12 @@
package edu.wpi.first.cscore;
/** A source that represents an Axis IP camera. */
/**
* A source that represents an Axis IP camera.
*
* @deprecated Use HttpCamera instead.
*/
@Deprecated(forRemoval = true, since = "2025")
public class AxisCamera extends HttpCamera {
private static String hostToUrl(String host) {
return "http://" + host + "/mjpg/video.mjpg";

View File

@@ -1,137 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.cscore;
import edu.wpi.first.util.RuntimeLoader;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Core;
/** CameraServer CV JNI. */
public class CameraServerCvJNI {
static boolean libraryLoaded = false;
static RuntimeLoader<Core> loader = null;
/** Sets whether JNI should be loaded in the static block. */
public static class Helper {
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
/**
* Returns true if the JNI should be loaded in the static block.
*
* @return True if the JNI should be loaded in the static block.
*/
public static boolean getExtractOnStaticLoad() {
return extractOnStaticLoad.get();
}
/**
* Sets whether the JNI should be loaded in the static block.
*
* @param load Whether the JNI should be loaded in the static block.
*/
public static void setExtractOnStaticLoad(boolean load) {
extractOnStaticLoad.set(load);
}
/** Utility class. */
private Helper() {}
}
static {
String opencvName = Core.NATIVE_LIBRARY_NAME;
if (Helper.getExtractOnStaticLoad()) {
try {
CameraServerJNI.forceLoad();
loader =
new RuntimeLoader<>(opencvName, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
loader.loadLibraryHashed();
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
libraryLoaded = true;
}
}
/**
* Force load the library.
*
* @throws IOException if library load failed
*/
public static synchronized void forceLoad() throws IOException {
if (libraryLoaded) {
return;
}
CameraServerJNI.forceLoad();
loader =
new RuntimeLoader<>(
Core.NATIVE_LIBRARY_NAME, RuntimeLoader.getDefaultExtractionRoot(), Core.class);
loader.loadLibrary();
libraryLoaded = true;
}
/**
* Creates a CV source.
*
* @param name Name.
* @param pixelFormat OpenCV pixel format.
* @param width Image width.
* @param height Image height.
* @param fps Frames per second.
* @return CV source.
*/
public static native int createCvSource(
String name, int pixelFormat, int width, int height, int fps);
/**
* Put source frame.
*
* @param source Source handle.
* @param imageNativeObj Image native object handle.
*/
public static native void putSourceFrame(int source, long imageNativeObj);
/**
* Creates a CV sink.
*
* @param name Name.
* @param pixelFormat OpenCV pixel format.
* @return CV sink handle.
*/
public static native int createCvSink(String name, int pixelFormat);
// /**
// * Creates a CV sink callback.
// *
// * @param name Name.
// * @param processFrame Process frame callback.
// */
// public static native int createCvSinkCallback(String name,
// void (*processFrame)(long time));
/**
* Returns sink frame handle.
*
* @param sink Sink handle.
* @param imageNativeObj Image native object handle.
* @return Sink frame handle.
*/
public static native long grabSinkFrame(int sink, long imageNativeObj);
/**
* Returns sink frame timeout in microseconds.
*
* @param sink Sink handle.
* @param imageNativeObj Image native object handle.
* @param timeout Timeout in seconds.
* @return Sink frame timeout in microseconds.
*/
public static native long grabSinkFrameTimeout(int sink, long imageNativeObj, double timeout);
/** Utility class. */
private CameraServerCvJNI() {}
}

View File

@@ -15,8 +15,6 @@ import java.util.function.Consumer;
public class CameraServerJNI {
static boolean libraryLoaded = false;
static RuntimeLoader<CameraServerJNI> loader = null;
/** Sets whether JNI should be loaded in the static block. */
public static class Helper {
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
@@ -46,10 +44,7 @@ public class CameraServerJNI {
static {
if (Helper.getExtractOnStaticLoad()) {
try {
loader =
new RuntimeLoader<>(
"cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
loader.loadLibrary();
RuntimeLoader.loadLibrary("cscorejni");
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
@@ -67,10 +62,7 @@ public class CameraServerJNI {
if (libraryLoaded) {
return;
}
loader =
new RuntimeLoader<>(
"cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
loader.loadLibrary();
RuntimeLoader.loadLibrary("cscorejni");
libraryLoaded = true;
}
@@ -212,6 +204,7 @@ public class CameraServerJNI {
* Creates a raw source.
*
* @param name Source name.
* @param isCv true for a Cv source.
* @param pixelFormat Pixel format.
* @param width Image width.
* @param height Image height.
@@ -219,7 +212,7 @@ public class CameraServerJNI {
* @return Raw source handle.
*/
public static native int createRawSource(
String name, int pixelFormat, int width, int height, int fps);
String name, boolean isCv, int pixelFormat, int width, int height, int fps);
//
// Source Functions
@@ -630,9 +623,10 @@ public class CameraServerJNI {
* Creates a raw sink.
*
* @param name Sink name.
* @param isCv true for a Cv source.
* @return Raw sink handle.
*/
public static native int createRawSink(String name);
public static native int createRawSink(String name, boolean isCv);
//
// Sink Functions

View File

@@ -5,6 +5,9 @@
package edu.wpi.first.cscore;
import edu.wpi.first.util.PixelFormat;
import edu.wpi.first.util.RawFrame;
import java.nio.ByteBuffer;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
/**
@@ -12,15 +15,42 @@ import org.opencv.core.Mat;
* OpenCV builds. For an alternate OpenCV, see the documentation how to build your own with RawSink.
*/
public class CvSink extends ImageSink {
private final RawFrame m_frame = new RawFrame();
private Mat m_tmpMat;
private ByteBuffer m_origByteBuffer;
private int m_width;
private int m_height;
private PixelFormat m_pixelFormat;
@Override
public void close() {
if (m_tmpMat != null) {
m_tmpMat.release();
}
m_frame.close();
super.close();
}
private int getCVFormat(PixelFormat pixelFormat) {
return switch (pixelFormat) {
case kYUYV, kRGB565, kY16, kUYVY -> CvType.CV_8UC2;
case kBGR -> CvType.CV_8UC3;
case kBGRA -> CvType.CV_8UC4;
case kGray, kMJPEG, kUnknown -> CvType.CV_8UC1;
};
}
/**
* Create a sink for accepting OpenCV images. WaitForFrame() must be called on the created sink to
* Create a sink for accepting OpenCV images. grabFrame() must be called on the created sink to
* get each new image.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Source pixel format
*/
public CvSink(String name, PixelFormat pixelFormat) {
super(CameraServerCvJNI.createCvSink(name, pixelFormat.getValue()));
super(CameraServerJNI.createRawSink(name, true));
m_pixelFormat = pixelFormat;
OpenCvLoader.forceStaticLoad();
}
/**
@@ -33,22 +63,9 @@ public class CvSink extends ImageSink {
this(name, PixelFormat.kBGR);
}
/// Create a sink for accepting OpenCV images in a separate thread.
/// A thread will be created that calls WaitForFrame() and calls the
/// processFrame() callback each time a new frame arrives.
/// @param name Source name (arbitrary unique identifier)
/// @param processFrame Frame processing function; will be called with a
/// time=0 if an error occurred. processFrame should call GetImage()
/// or GetError() as needed, but should not call (except in very
/// unusual circumstances) WaitForImage().
// public CvSink(wpi::StringRef name,
// std::function<void(uint64_t time)> processFrame) {
// super(CameraServerJNI.createCvSinkCallback(name, processFrame));
// }
/**
* Wait for the next frame and get the image. Times out (returning 0) after 0.225 seconds. The
* provided image will have three 3-bit channels stored in BGR order.
* provided image will have the pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @return Frame time, or 0 on error (call GetError() to obtain the error message)
@@ -59,7 +76,7 @@ public class CvSink extends ImageSink {
/**
* Wait for the next frame and get the image. Times out (returning 0) after timeout seconds. The
* provided image will have three 3-bit channels stored in BGR order.
* provided image will have the pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @param timeout Retrieval timeout in seconds.
@@ -67,18 +84,140 @@ public class CvSink extends ImageSink {
* is in 1 us increments.
*/
public long grabFrame(Mat image, double timeout) {
return CameraServerCvJNI.grabSinkFrameTimeout(m_handle, image.nativeObj, timeout);
long rv = grabFrameDirect(timeout);
if (rv <= 0) {
return rv;
}
m_tmpMat.copyTo(image);
return rv;
}
/**
* Wait for the next frame and get the image. May block forever. The provided image will have
* three 3-bit channels stored in BGR order.
* Wait for the next frame and get the image. May block forever. The provided image will have the
* pixelFormat this class was constructed with.
*
* @param image Where to store the image.
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
public long grabFrameNoTimeout(Mat image) {
return CameraServerCvJNI.grabSinkFrame(m_handle, image.nativeObj);
long rv = grabFrameNoTimeoutDirect();
if (rv <= 0) {
return rv;
}
m_tmpMat.copyTo(image);
return rv;
}
/**
* Get the direct backing mat for this sink.
*
* <p>This mat can be invalidated any time any of the grab* methods are called, or when the CvSink
* is closed.
*
* @return The backing mat.
*/
public Mat getDirectMat() {
return m_tmpMat;
}
/**
* Wait for the next frame and store the image. Times out (returning 0) after 0.225 seconds. The
* provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to
* grab the image.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error message)
*/
public long grabFrameDirect() {
return grabFrameDirect(0.225);
}
/**
* Wait for the next frame and store the image. Times out (returning 0) after timeout seconds. The
* provided image will have the pixelFormat this class was constructed with. Use getDirectMat() to
* grab the image.
*
* @param timeout Retrieval timeout in seconds.
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public long grabFrameDirect(double timeout) {
m_frame.setInfo(0, 0, 0, m_pixelFormat);
long rv =
CameraServerJNI.grabRawSinkFrameTimeout(m_handle, m_frame, m_frame.getNativeObj(), timeout);
if (rv <= 0) {
return rv;
}
if (m_frame.getData() != m_origByteBuffer
|| m_width != m_frame.getWidth()
|| m_height != m_frame.getHeight()
|| m_pixelFormat != m_frame.getPixelFormat()) {
m_origByteBuffer = m_frame.getData();
m_height = m_frame.getHeight();
m_width = m_frame.getWidth();
m_pixelFormat = m_frame.getPixelFormat();
if (m_frame.getStride() == 0) {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer);
} else {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer,
m_frame.getStride());
}
}
return rv;
}
/**
* Wait for the next frame and store the image. May block forever. The provided image will have
* the pixelFormat this class was constructed with. Use getDirectMat() to grab the image.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error message); the frame time
* is in 1 us increments.
*/
@SuppressWarnings("PMD.CompareObjectsWithEquals")
public long grabFrameNoTimeoutDirect() {
m_frame.setInfo(0, 0, 0, m_pixelFormat);
long rv = CameraServerJNI.grabRawSinkFrame(m_handle, m_frame, m_frame.getNativeObj());
if (rv <= 0) {
return rv;
}
if (m_frame.getData() != m_origByteBuffer
|| m_width != m_frame.getWidth()
|| m_height != m_frame.getHeight()
|| m_pixelFormat != m_frame.getPixelFormat()) {
m_origByteBuffer = m_frame.getData();
m_height = m_frame.getHeight();
m_width = m_frame.getWidth();
m_pixelFormat = m_frame.getPixelFormat();
if (m_frame.getStride() == 0) {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer);
} else {
m_tmpMat =
new Mat(
m_frame.getHeight(),
m_frame.getWidth(),
getCVFormat(m_pixelFormat),
m_origByteBuffer,
m_frame.getStride());
}
}
return rv;
}
}

View File

@@ -20,8 +20,9 @@ public class CvSource extends ImageSource {
*/
public CvSource(String name, VideoMode mode) {
super(
CameraServerCvJNI.createCvSource(
name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
CameraServerJNI.createRawSource(
name, true, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
OpenCvLoader.forceStaticLoad();
}
/**
@@ -34,19 +35,140 @@ public class CvSource extends ImageSource {
* @param fps fps
*/
public CvSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
super(CameraServerCvJNI.createCvSource(name, pixelFormat.getValue(), width, height, fps));
super(CameraServerJNI.createRawSource(name, true, pixelFormat.getValue(), width, height, fps));
OpenCvLoader.forceStaticLoad();
}
/**
* Put an OpenCV image and notify sinks
*
* <p>The image format is guessed from the number of channels. The channel mapping is as follows.
* 1: kGray 2: kYUYV 3: BGR 4: BGRA Any other channel numbers will throw an error. If your image
* is an in alternate format, use the overload that takes a PixelFormat.
*
* @param image OpenCV Image
*/
public void putFrame(Mat image) {
// We only support 8 bit channels
boolean cleanupRequired = false;
Mat finalImage;
if (image.depth() == 0) {
finalImage = image;
} else {
finalImage = new Mat();
image.convertTo(finalImage, 0);
cleanupRequired = true;
}
try {
int channels = finalImage.channels();
PixelFormat format =
switch (channels) {
case 1 -> PixelFormat.kGray; // 1 channel is assumed Grayscale
case 2 -> PixelFormat.kYUYV; // 2 channels is assumed YUYV
case 3 -> PixelFormat.kBGR; // 3 channels is assumed BGR
case 4 -> PixelFormat.kBGRA; // 4 channels is assumed BGRA
default -> throw new VideoException(
"Unable to get pixel format for " + channels + " channels");
};
putFrame(finalImage, format, true);
} finally {
if (cleanupRequired) {
finalImage.release();
}
}
}
/**
* Put an OpenCV image and notify sinks.
*
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images are supported. If the
* format, depth or channel order is different, use Mat.convertTo() and/or cvtColor() to convert
* it first.
* <p>The format of the Mat must match the PixelFormat. You will corrupt memory if they dont. With
* skipVerification false, we will verify the number of channels matches the pixel format. If
* skipVerification is true, this step is skipped and is passed straight through.
*
* @param image OpenCV image
* @param format The pixel format of the image
* @param skipVerification skip verifying pixel format
*/
public void putFrame(Mat image) {
CameraServerCvJNI.putSourceFrame(m_handle, image.nativeObj);
public void putFrame(Mat image, PixelFormat format, boolean skipVerification) {
// We only support 8-bit images, convert if necessary
boolean cleanupRequired = false;
Mat finalImage;
if (image.depth() == 0) {
finalImage = image;
} else {
finalImage = new Mat();
image.convertTo(finalImage, 0);
cleanupRequired = true;
}
try {
if (!skipVerification) {
verifyFormat(finalImage, format);
}
long step = image.step1() * image.elemSize1();
CameraServerJNI.putRawSourceFrameData(
m_handle,
finalImage.dataAddr(),
(int) finalImage.total() * finalImage.channels(),
finalImage.width(),
finalImage.height(),
(int) step,
format.getValue());
} finally {
if (cleanupRequired) {
finalImage.release();
}
}
}
private void verifyFormat(Mat image, PixelFormat pixelFormat) {
int channels = image.channels();
switch (pixelFormat) {
case kBGR:
if (channels == 3) {
return;
}
break;
case kBGRA:
if (channels == 4) {
return;
}
break;
case kGray:
if (channels == 1) {
return;
}
break;
case kRGB565:
if (channels == 2) {
return;
}
break;
case kUYVY:
if (channels == 2) {
return;
}
break;
case kY16:
if (channels == 2) {
return;
}
break;
case kYUYV:
if (channels == 2) {
return;
}
break;
case kMJPEG:
if (channels == 1) {
return;
}
break;
default:
break;
}
}
}

View File

@@ -40,16 +40,12 @@ public class HttpCamera extends VideoCamera {
* @return The kind
*/
public static HttpCameraKind getHttpCameraKindFromInt(int kind) {
switch (kind) {
case 1:
return HttpCameraKind.kMJPGStreamer;
case 2:
return HttpCameraKind.kCSCore;
case 3:
return HttpCameraKind.kAxis;
default:
return HttpCameraKind.kUnknown;
}
return switch (kind) {
case 1 -> HttpCameraKind.kMJPGStreamer;
case 2 -> HttpCameraKind.kCSCore;
case 3 -> HttpCameraKind.kAxis;
default -> HttpCameraKind.kUnknown;
};
}
/**

View File

@@ -0,0 +1,79 @@
// 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.cscore;
import edu.wpi.first.util.RuntimeLoader;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Core;
/** OpenCV Native Loader. */
public final class OpenCvLoader {
@SuppressWarnings("PMD.MutableStaticState")
static boolean libraryLoaded;
/** Sets whether JNI should be loaded in the static block. */
public static final class Helper {
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
/**
* Returns true if the JNI should be loaded in the static block.
*
* @return True if the JNI should be loaded in the static block.
*/
public static boolean getExtractOnStaticLoad() {
return extractOnStaticLoad.get();
}
/**
* Sets whether the JNI should be loaded in the static block.
*
* @param load Whether the JNI should be loaded in the static block.
*/
public static void setExtractOnStaticLoad(boolean load) {
extractOnStaticLoad.set(load);
}
/** Utility class. */
private Helper() {}
}
static {
if (Helper.getExtractOnStaticLoad()) {
try {
RuntimeLoader.loadLibrary(Core.NATIVE_LIBRARY_NAME);
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
libraryLoaded = true;
}
}
/**
* Forces a static load.
*
* @return a garbage value
*/
public static int forceStaticLoad() {
return libraryLoaded ? 1 : 0;
}
/**
* Force load the library.
*
* @throws IOException if library load failed
*/
public static synchronized void forceLoad() throws IOException {
if (libraryLoaded) {
return;
}
RuntimeLoader.loadLibrary(Core.NATIVE_LIBRARY_NAME);
libraryLoaded = true;
}
/** Utility class. */
private OpenCvLoader() {}
}

View File

@@ -75,48 +75,28 @@ public class VideoEvent {
* @return The kind
*/
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 0x0001:
return Kind.kSourceCreated;
case 0x0002:
return Kind.kSourceDestroyed;
case 0x0004:
return Kind.kSourceConnected;
case 0x0008:
return Kind.kSourceDisconnected;
case 0x0010:
return Kind.kSourceVideoModesUpdated;
case 0x0020:
return Kind.kSourceVideoModeChanged;
case 0x0040:
return Kind.kSourcePropertyCreated;
case 0x0080:
return Kind.kSourcePropertyValueUpdated;
case 0x0100:
return Kind.kSourcePropertyChoicesUpdated;
case 0x0200:
return Kind.kSinkSourceChanged;
case 0x0400:
return Kind.kSinkCreated;
case 0x0800:
return Kind.kSinkDestroyed;
case 0x1000:
return Kind.kSinkEnabled;
case 0x2000:
return Kind.kSinkDisabled;
case 0x4000:
return Kind.kNetworkInterfacesChanged;
case 0x10000:
return Kind.kSinkPropertyCreated;
case 0x20000:
return Kind.kSinkPropertyValueUpdated;
case 0x40000:
return Kind.kSinkPropertyChoicesUpdated;
case 0x80000:
return Kind.kUsbCamerasChanged;
default:
return Kind.kUnknown;
}
return switch (kind) {
case 0x0001 -> Kind.kSourceCreated;
case 0x0002 -> Kind.kSourceDestroyed;
case 0x0004 -> Kind.kSourceConnected;
case 0x0008 -> Kind.kSourceDisconnected;
case 0x0010 -> Kind.kSourceVideoModesUpdated;
case 0x0020 -> Kind.kSourceVideoModeChanged;
case 0x0040 -> Kind.kSourcePropertyCreated;
case 0x0080 -> Kind.kSourcePropertyValueUpdated;
case 0x0100 -> Kind.kSourcePropertyChoicesUpdated;
case 0x0200 -> Kind.kSinkSourceChanged;
case 0x0400 -> Kind.kSinkCreated;
case 0x0800 -> Kind.kSinkDestroyed;
case 0x1000 -> Kind.kSinkEnabled;
case 0x2000 -> Kind.kSinkDisabled;
case 0x4000 -> Kind.kNetworkInterfacesChanged;
case 0x10000 -> Kind.kSinkPropertyCreated;
case 0x20000 -> Kind.kSinkPropertyValueUpdated;
case 0x40000 -> Kind.kSinkPropertyChoicesUpdated;
case 0x80000 -> Kind.kUsbCamerasChanged;
default -> Kind.kUnknown;
};
}
VideoEvent(

View File

@@ -64,7 +64,10 @@ public class VideoListener implements AutoCloseable {
private static final ReentrantLock s_lock = new ReentrantLock();
private static final Map<Integer, Consumer<VideoEvent>> s_listeners = new HashMap<>();
@SuppressWarnings("PMD.SingularField")
private static Thread s_thread;
private static int s_poller;
private static boolean s_waitQueue;
private static final Condition s_waitQueueCond = s_lock.newCondition();

View File

@@ -42,18 +42,13 @@ public class VideoProperty {
* @return The kind
*/
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 1:
return Kind.kBoolean;
case 2:
return Kind.kInteger;
case 4:
return Kind.kString;
case 8:
return Kind.kEnum;
default:
return Kind.kNone;
}
return switch (kind) {
case 1 -> Kind.kBoolean;
case 2 -> Kind.kInteger;
case 4 -> Kind.kString;
case 8 -> Kind.kEnum;
default -> Kind.kNone;
};
}
/**
@@ -217,5 +212,5 @@ public class VideoProperty {
}
int m_handle;
private Kind m_kind;
private final Kind m_kind;
}

View File

@@ -43,14 +43,12 @@ public class VideoSink implements AutoCloseable {
* @return The kind
*/
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 2:
return Kind.kMjpeg;
case 4:
return Kind.kCv;
default:
return Kind.kUnknown;
}
return switch (kind) {
case 2 -> Kind.kMjpeg;
case 4 -> Kind.kCv;
case 8 -> Kind.kRaw;
default -> Kind.kUnknown;
};
}
/**

View File

@@ -79,16 +79,13 @@ public class VideoSource implements AutoCloseable {
* @return The kind
*/
public static Kind getKindFromInt(int kind) {
switch (kind) {
case 1:
return Kind.kUsb;
case 2:
return Kind.kHttp;
case 4:
return Kind.kCv;
default:
return Kind.kUnknown;
}
return switch (kind) {
case 1 -> Kind.kUsb;
case 2 -> Kind.kHttp;
case 4 -> Kind.kCv;
case 8 -> Kind.kRaw;
default -> Kind.kUnknown;
};
}
/**

View File

@@ -22,7 +22,7 @@ public class RawSink extends ImageSink {
* @param name Source name (arbitrary unique identifier)
*/
public RawSink(String name) {
super(CameraServerJNI.createRawSink(name));
super(CameraServerJNI.createRawSink(name, false));
}
/**

View File

@@ -26,7 +26,7 @@ public class RawSource extends ImageSource {
public RawSource(String name, VideoMode mode) {
super(
CameraServerJNI.createRawSource(
name, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
name, false, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
}
/**
@@ -39,7 +39,7 @@ public class RawSource extends ImageSource {
* @param fps fps
*/
public RawSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
super(CameraServerJNI.createRawSource(name, pixelFormat.getValue(), width, height, fps));
super(CameraServerJNI.createRawSource(name, false, pixelFormat.getValue(), width, height, fps));
}
/**

View File

@@ -104,3 +104,144 @@ void ConfigurableSourceImpl::SetEnumPropertyChoices(
prop->name, property, CS_PROP_ENUM,
prop->value, {});
}
namespace cs {
static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW;
void NotifySourceError(CS_Source source, std::string_view msg,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source).NotifyError(msg);
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source).SetConnected(connected);
}
void SetSourceDescription(CS_Source source, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<ConfigurableSourceImpl&>(*data->source)
.SetDescription(description);
}
CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<ConfigurableSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value);
return Handle{source, property, Handle::kProperty};
}
CS_Property CreateSourcePropertyCallback(
CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<ConfigurableSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value, onChange);
return Handle{source, property, Handle::kProperty};
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
std::span<const std::string> choices,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
// Get property index; also validate the source owns this property
Handle handle{property};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;
}
int propertyIndex = handle.GetIndex();
static_cast<ConfigurableSourceImpl&>(*data->source)
.SetEnumPropertyChoices(propertyIndex, choices, status);
}
} // namespace cs
extern "C" {
void CS_NotifySourceError(CS_Source source, const struct WPI_String* msg,
CS_Status* status) {
return cs::NotifySourceError(source, wpi::to_string_view(msg), status);
}
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}
void CS_SetSourceDescription(CS_Source source,
const struct WPI_String* description,
CS_Status* status) {
return cs::SetSourceDescription(source, wpi::to_string_view(description),
status);
}
CS_Property CS_CreateSourceProperty(CS_Source source,
const struct WPI_String* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, wpi::to_string_view(name), kind,
minimum, maximum, step, defaultValue, value,
status);
}
CS_Property CS_CreateSourcePropertyCallback(
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value, void* data,
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
return cs::CreateSourcePropertyCallback(
source, name, kind, minimum, maximum, step, defaultValue, value,
[=](CS_Property property) { onChange(data, property); }, status);
}
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const struct WPI_String* choices,
int count, CS_Status* status) {
wpi::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.emplace_back(wpi::to_string_view(&choices[i]));
}
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
} // extern "C"

View File

@@ -33,7 +33,7 @@ class ConfigurableSourceImpl : public SourceImpl {
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
// OpenCV-specific functions
// Frame based specific functions
void NotifyError(std::string_view msg);
int CreateProperty(std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value);

View File

@@ -1,270 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "CvSinkImpl.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <wpi/SmallString.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
VideoMode::PixelFormat pixelFormat)
: SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} {
m_active = true;
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
}
CvSinkImpl::CvSinkImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame)
: SinkImpl{name, logger, notifier, telemetry}, m_pixelFormat{pixelFormat} {}
CvSinkImpl::~CvSinkImpl() {
Stop();
}
void CvSinkImpl::Stop() {
m_active = false;
// wake up any waiters by forcing an empty frame to be sent
if (auto source = GetSource()) {
source->Wakeup();
}
// join thread
if (m_thread.joinable()) {
m_thread.join();
}
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image, m_pixelFormat)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
uint64_t CvSinkImpl::GrabFrame(cv::Mat& image, double timeout) {
SetEnabled(true);
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
auto frame = source->GetNextFrame(timeout); // blocks
if (!frame) {
// Bad frame; sleep for 20 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0; // signal error
}
if (!frame.GetCv(image, m_pixelFormat)) {
// Shouldn't happen, but just in case...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
return 0;
}
return frame.GetTime();
}
// Send HTTP response and a stream of JPG-frames
void CvSinkImpl::ThreadMain() {
Enable();
while (m_active) {
auto source = GetSource();
if (!source) {
// Source disconnected; sleep for one second
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
SDEBUG4("waiting for frame");
Frame frame = source->GetNextFrame(); // blocks
if (!m_active) {
break;
}
if (!frame) {
// Bad frame; sleep for 10 ms so we don't consume all processor time.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// TODO m_processFrame();
}
Disable();
}
namespace cs {
CS_Sink CreateCvSink(std::string_view name, VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, pixelFormat));
}
CS_Sink CreateCvSinkCallback(std::string_view name,
VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV,
std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, pixelFormat, processFrame));
}
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
void SetSinkDescription(CS_Sink sink, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetDescription(description);
}
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image);
}
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<CvSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError();
}
std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return {};
}
return static_cast<CvSinkImpl&>(*data->sink).GetError(buf);
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSinkImpl&>(*data->sink).SetEnabled(enabled);
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateCvSink(const char* name, enum WPI_PixelFormat pixelFormat,
CS_Status* status) {
return cs::CreateCvSink(
name, static_cast<VideoMode::PixelFormat>(pixelFormat), status);
}
CS_Sink CS_CreateCvSinkCallback(const char* name,
enum WPI_PixelFormat pixelFormat, void* data,
void (*processFrame)(void* data, uint64_t time),
CS_Status* status) {
return cs::CreateCvSinkCallback(
name, static_cast<VideoMode::PixelFormat>(pixelFormat),
[=](uint64_t time) { processFrame(data, time); }, status);
}
void CS_SetSinkDescription(CS_Sink sink, const char* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, description, status);
}
#if CV_VERSION_MAJOR < 4
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrame(sink, mat, status);
}
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::GrabSinkFrameTimeout(sink, mat, timeout, status);
}
#endif // CV_VERSION_MAJOR < 4
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status) {
return cs::GrabSinkFrame(sink, *image, status);
}
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status) {
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}
char* CS_GetSinkError(CS_Sink sink, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkError(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
}
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
return cs::SetSinkEnabled(sink, enabled, status);
}
} // extern "C"

View File

@@ -1,50 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CVSINKIMPL_H_
#define CSCORE_CVSINKIMPL_H_
#include <stdint.h>
#include <atomic>
#include <functional>
#include <string_view>
#include <thread>
#include <opencv2/core/core.hpp>
#include <wpi/condition_variable.h>
#include "Frame.h"
#include "SinkImpl.h"
namespace cs {
class SourceImpl;
class CvSinkImpl : public SinkImpl {
public:
CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, VideoMode::PixelFormat pixelFormat);
CvSinkImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, VideoMode::PixelFormat pixelFormat,
std::function<void(uint64_t time)> processFrame);
~CvSinkImpl() override;
void Stop();
uint64_t GrabFrame(cv::Mat& image);
uint64_t GrabFrame(cv::Mat& image, double timeout);
private:
void ThreadMain();
std::atomic_bool m_active; // set to false to terminate threads
std::thread m_thread;
std::function<void(uint64_t time)> m_processFrame;
VideoMode::PixelFormat m_pixelFormat;
};
} // namespace cs
#endif // CSCORE_CVSINKIMPL_H_

View File

@@ -1,232 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "CvSourceImpl.h"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
CvSourceImpl::CvSourceImpl(std::string_view name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const VideoMode& mode)
: ConfigurableSourceImpl{name, logger, notifier, telemetry, mode} {}
CvSourceImpl::~CvSourceImpl() = default;
void CvSourceImpl::PutFrame(cv::Mat& image) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U) {
finalImage = image;
} else {
image.convertTo(finalImage, CV_8U);
}
std::unique_ptr<Image> dest;
switch (image.channels()) {
case 1:
dest =
AllocImage(VideoMode::kGray, image.cols, image.rows, image.total());
finalImage.copyTo(dest->AsMat());
break;
case 3:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
finalImage.copyTo(dest->AsMat());
break;
case 4:
dest = AllocImage(VideoMode::kBGR, image.cols, image.rows,
image.total() * 3);
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
break;
default:
SERROR("PutFrame: {}-channel images not supported", image.channels());
return;
}
SourceImpl::PutFrame(std::move(dest), wpi::Now());
}
namespace cs {
CS_Source CreateCvSource(std::string_view name, const VideoMode& mode,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_CV, std::make_shared<CvSourceImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, mode));
}
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).PutFrame(image);
}
static constexpr unsigned SourceMask = CS_SINK_CV | CS_SINK_RAW;
void NotifySourceError(CS_Source source, std::string_view msg,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).NotifyError(msg);
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetConnected(connected);
}
void SetSourceDescription(CS_Source source, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<CvSourceImpl&>(*data->source).SetDescription(description);
}
CS_Property CreateSourceProperty(CS_Source source, std::string_view name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value);
return Handle{source, property, Handle::kProperty};
}
CS_Property CreateSourcePropertyCallback(
CS_Source source, std::string_view name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return -1;
}
int property = static_cast<CvSourceImpl&>(*data->source)
.CreateProperty(name, kind, minimum, maximum, step,
defaultValue, value, onChange);
return Handle{source, property, Handle::kProperty};
}
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
std::span<const std::string> choices,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
// Get property index; also validate the source owns this property
Handle handle{property};
int i = handle.GetParentIndex();
if (i < 0) {
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;
}
int propertyIndex = handle.GetIndex();
static_cast<CvSourceImpl&>(*data->source)
.SetEnumPropertyChoices(propertyIndex, choices, status);
}
} // namespace cs
extern "C" {
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
CS_Status* status) {
return cs::CreateCvSource(name, static_cast<const cs::VideoMode&>(*mode),
status);
}
#if CV_VERSION_MAJOR < 4
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status) {
auto mat = cv::cvarrToMat(image);
return cs::PutSourceFrame(source, mat, status);
}
#endif // CV_VERSION_MAJOR < 4
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status) {
return cs::PutSourceFrame(source, *image, status);
}
void CS_NotifySourceError(CS_Source source, const char* msg,
CS_Status* status) {
return cs::NotifySourceError(source, msg, status);
}
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status) {
return cs::SetSourceConnected(source, connected, status);
}
void CS_SetSourceDescription(CS_Source source, const char* description,
CS_Status* status) {
return cs::SetSourceDescription(source, description, status);
}
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status) {
return cs::CreateSourceProperty(source, name, kind, minimum, maximum, step,
defaultValue, value, status);
}
CS_Property CS_CreateSourcePropertyCallback(
CS_Source source, const char* name, enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value, void* data,
void (*onChange)(void* data, CS_Property property), CS_Status* status) {
return cs::CreateSourcePropertyCallback(
source, name, kind, minimum, maximum, step, defaultValue, value,
[=](CS_Property property) { onChange(data, property); }, status);
}
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status) {
wpi::SmallVector<std::string, 8> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(choices[i]);
}
return cs::SetSourceEnumPropertyChoices(source, property, vec, status);
}
} // extern "C"

View File

@@ -1,37 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CVSOURCEIMPL_H_
#define CSCORE_CVSOURCEIMPL_H_
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <opencv2/core/core.hpp>
#include "ConfigurableSourceImpl.h"
#include "SourceImpl.h"
namespace cs {
class CvSourceImpl : public ConfigurableSourceImpl {
public:
CvSourceImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, const VideoMode& mode);
~CvSourceImpl() override;
// OpenCV-specific functions
void PutFrame(cv::Mat& image);
private:
std::atomic_bool m_connected{true};
};
} // namespace cs
#endif // CSCORE_CVSOURCEIMPL_H_

View File

@@ -314,6 +314,54 @@ Image* Frame::ConvertImpl(Image* image, VideoMode::PixelFormat pixelFormat,
}
}
break;
case VideoMode::kBGRA:
// If source is RGB565, YUYV, UYVY, Gray or Y16, need to convert to BGR
// first
if (cur->pixelFormat == VideoMode::kRGB565) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
cur = newImage;
} else {
cur = ConvertRGB565ToBGR(cur);
}
} else if (cur->pixelFormat == VideoMode::kYUYV) {
// Check to see if BGR version already exists...
if (Image* newImage =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
cur = newImage;
} 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 =
GetExistingImage(cur->width, cur->height, VideoMode::kBGR)) {
cur = newImage;
} else {
cur = ConvertGrayToBGR(cur);
}
} 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 = ConvertGrayToBGR(ConvertY16ToGray(cur));
}
}
return ConvertBGRToBGRA(cur);
case VideoMode::kYUYV:
case VideoMode::kUYVY:
default:
@@ -664,6 +712,28 @@ Image* Frame::ConvertY16ToGray(Image* image) {
return rv;
}
Image* Frame::ConvertBGRToBGRA(Image* image) {
if (!image || image->pixelFormat != VideoMode::kBGR) {
return nullptr;
}
// Allocate a RGB565 image
auto newImage =
m_impl->source.AllocImage(VideoMode::kBGRA, image->width, image->height,
image->width * image->height * 4);
// Convert
cv::cvtColor(image->AsMat(), newImage->AsMat(), cv::COLOR_BGR2BGRA);
// 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) {
@@ -727,3 +797,16 @@ void Frame::ReleaseFrame() {
m_impl->source.ReleaseFrameImpl(std::unique_ptr<Impl>(m_impl));
m_impl = nullptr;
}
namespace cs {
std::unique_ptr<Image> CreateImageFromBGRA(cs::SourceImpl* source, size_t width,
size_t height, size_t stride,
const uint8_t* data) {
cv::Mat finalImage{static_cast<int>(height), static_cast<int>(width), CV_8UC4,
const_cast<uint8_t*>(data), stride};
std::unique_ptr<Image> dest = source->AllocImage(
VideoMode::PixelFormat::kBGR, width, height, width * height * 3);
cv::cvtColor(finalImage, dest->AsMat(), cv::COLOR_BGRA2BGR);
return dest;
}
} // namespace cs

View File

@@ -22,6 +22,10 @@ namespace cs {
class SourceImpl;
std::unique_ptr<Image> CreateImageFromBGRA(cs::SourceImpl* source, size_t width,
size_t height, size_t stride,
const uint8_t* data);
class Frame {
friend class SourceImpl;
@@ -206,6 +210,7 @@ class Frame {
Image* ConvertGrayToMJPEG(Image* image, int quality);
Image* ConvertGrayToY16(Image* image);
Image* ConvertY16ToGray(Image* image);
Image* ConvertBGRToBGRA(Image* image);
Image* GetImage(int width, int height, VideoMode::PixelFormat pixelFormat) {
if (pixelFormat == VideoMode::kMJPEG) {

View File

@@ -634,55 +634,47 @@ std::vector<std::string> GetHttpCameraUrls(CS_Source source,
extern "C" {
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
CS_Source CS_CreateHttpCamera(const struct WPI_String* name,
const struct WPI_String* url,
CS_HttpCameraKind kind, CS_Status* status) {
return cs::CreateHttpCamera(name, url, kind, status);
return cs::CreateHttpCamera(wpi::to_string_view(name),
wpi::to_string_view(url), kind, status);
}
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
int count, CS_HttpCameraKind kind,
CS_Status* status) {
CS_Source CS_CreateHttpCameraMulti(const struct WPI_String* name,
const struct WPI_String* urls, int count,
CS_HttpCameraKind kind, CS_Status* status) {
wpi::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(urls[i]);
vec.emplace_back(wpi::to_string_view(&urls[i]));
}
return cs::CreateHttpCamera(name, vec, kind, status);
return cs::CreateHttpCamera(wpi::to_string_view(name), vec, kind, status);
}
CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source, CS_Status* status) {
return cs::GetHttpCameraKind(source, status);
}
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
CS_Status* status) {
void CS_SetHttpCameraUrls(CS_Source source, const struct WPI_String* urls,
int count, CS_Status* status) {
wpi::SmallVector<std::string, 4> vec;
vec.reserve(count);
for (int i = 0; i < count; ++i) {
vec.push_back(urls[i]);
vec.emplace_back(wpi::to_string_view(&urls[i]));
}
cs::SetHttpCameraUrls(source, vec, status);
}
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status) {
WPI_String* CS_GetHttpCameraUrls(CS_Source source, int* count,
CS_Status* status) {
auto urls = cs::GetHttpCameraUrls(source, status);
char** out =
static_cast<char**>(wpi::safe_malloc(urls.size() * sizeof(char*)));
WPI_String* out = WPI_AllocateStringArray(urls.size());
*count = urls.size();
for (size_t i = 0; i < urls.size(); ++i) {
out[i] = cs::ConvertToC(urls[i]);
cs::ConvertToC(&out[i], urls[i]);
}
return out;
}
void CS_FreeHttpCameraUrls(char** urls, int count) {
if (!urls) {
return;
}
for (int i = 0; i < count; ++i) {
std::free(urls[i]);
}
std::free(urls);
}
} // extern "C"

View File

@@ -63,6 +63,9 @@ class Image {
case VideoMode::kBGR:
type = CV_8UC3;
break;
case VideoMode::kBGRA:
type = CV_8UC4;
break;
case VideoMode::kGray:
case VideoMode::kMJPEG:
default:
@@ -81,6 +84,8 @@ class Image {
return 2 * width;
case VideoMode::kBGR:
return 3 * width;
case VideoMode::kBGRA:
return 4 * width;
case VideoMode::kGray:
return width;
case VideoMode::kMJPEG:

View File

@@ -8,13 +8,14 @@
#include <fmt/format.h>
#include <wpi/fs.h>
#include <wpi/print.h>
using namespace cs;
static void def_log_func(unsigned int level, const char* file,
unsigned int line, const char* msg) {
if (level == 20) {
fmt::print(stderr, "CS: {}\n", msg);
wpi::print(stderr, "CS: {}\n", msg);
return;
}
@@ -28,7 +29,7 @@ static void def_log_func(unsigned int level, const char* file,
} else {
return;
}
fmt::print(stderr, "CS: {}: {} ({}:{})\n", levelmsg, msg,
wpi::print(stderr, "CS: {}: {} ({}:{})\n", levelmsg, msg,
fs::path{file}.filename().string(), line);
}

View File

@@ -6,10 +6,10 @@
#include <chrono>
#include <fmt/format.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/fmt/raw_ostream.h>
#include <wpi/print.h>
#include <wpinet/HttpUtil.h>
#include <wpinet/TCPAcceptor.h>
#include <wpinet/raw_socket_istream.h>
@@ -135,7 +135,7 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
static void SendHeader(wpi::raw_ostream& os, int code,
std::string_view codeText, std::string_view contentType,
std::string_view extra = {}) {
fmt::print(os, "HTTP/1.0 {} {}\r\n", code, codeText);
wpi::print(os, "HTTP/1.0 {} {}\r\n", code, codeText);
os << "Connection: close\r\n"
"Server: CameraServer/1.0\r\n"
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
@@ -302,7 +302,7 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(wpi::raw_ostream& os,
case CS_PROP_INTEGER:
case CS_PROP_ENUM: {
if (auto v = wpi::parse_integer<int>(value, 10)) {
fmt::print(response, "{}: {}\r\n", param, v.value());
wpi::print(response, "{}: {}\r\n", param, v.value());
SDEBUG4("HTTP parameter \"{}\" value {}", param, value);
source.SetProperty(prop, v.value(), &status);
} else {
@@ -356,10 +356,10 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
continue;
}
auto kind = source.GetPropertyKind(prop);
fmt::print(os, "<p /><label for=\"{0}\">{0}</label>\n", name);
wpi::print(os, "<p /><label for=\"{0}\">{0}</label>\n", name);
switch (kind) {
case CS_PROP_BOOLEAN:
fmt::print(os,
wpi::print(os,
"<input id=\"{0}\" type=\"checkbox\" "
"onclick=\"update('{0}', this.checked ? 1 : 0)\" ",
name);
@@ -374,12 +374,12 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
auto min = source.GetPropertyMin(prop, &status);
auto max = source.GetPropertyMax(prop, &status);
auto step = source.GetPropertyStep(prop, &status);
fmt::print(os,
wpi::print(os,
"<input type=\"range\" min=\"{1}\" max=\"{2}\" "
"value=\"{3}\" id=\"{0}\" step=\"{4}\" "
"oninput=\"updateInt('#{0}op', '{0}', value)\" />\n",
name, min, max, valI, step);
fmt::print(os, "<output for=\"{0}\" id=\"{0}op\">{1}</output>\n", name,
wpi::print(os, "<output for=\"{0}\" id=\"{0}op\">{1}</output>\n", name,
valI);
break;
}
@@ -397,25 +397,25 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
for (char ch : *choice) {
ch_name.push_back(wpi::isPrint(ch) ? ch : ' ');
}
fmt::print(os,
wpi::print(os,
"<input id=\"{0}{1}\" type=\"radio\" name=\"{0}\" "
"value=\"{2}\" onclick=\"update('{0}', {1})\"",
name, j, ch_name.str());
if (j == valE) {
os << " checked";
}
fmt::print(os, " /><label for=\"{}{}\">{}</label>\n", name, j,
wpi::print(os, " /><label for=\"{}{}\">{}</label>\n", name, j,
ch_name.str());
}
break;
}
case CS_PROP_STRING: {
wpi::SmallString<128> strval_buf;
fmt::print(os,
wpi::print(os,
"<input type=\"text\" id=\"{0}box\" name=\"{0}\" "
"value=\"{1}\" />\n",
name, source.GetStringProperty(prop, strval_buf, &status));
fmt::print(os,
wpi::print(os,
"<input type=\"button\" value =\"Submit\" "
"onclick=\"update('{0}', {0}box.value)\" />\n",
name);
@@ -438,9 +438,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
os << "<p>Supported Video Modes:</p>\n";
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
os << "<tr><th>Pixel Format</th>"
<< "<th>Width</th>"
<< "<th>Height</th>"
os << "<tr><th>Pixel Format</th>" << "<th>Width</th>" << "<th>Height</th>"
<< "<th>FPS</th></tr>";
for (auto mode : source.EnumerateVideoModes(&status)) {
os << "<tr><td>";
@@ -457,6 +455,9 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
case VideoMode::kBGR:
os << "BGR";
break;
case VideoMode::kBGRA:
os << "BGRA";
break;
case VideoMode::kGray:
os << "gray";
break;
@@ -470,7 +471,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
os << "unknown";
break;
}
fmt::print(os, "</td><td>{}</td><td>{}</td><td>{}</td></tr>", mode.width,
wpi::print(os, "</td><td>{}</td><td>{}</td><td>{}</td></tr>", mode.width,
mode.height, mode.fps);
}
os << "</table>\n";
@@ -499,21 +500,21 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
wpi::SmallString<128> name_buf;
auto name = source.GetPropertyName(prop, name_buf, &status);
auto kind = source.GetPropertyKind(prop);
fmt::print(os, "\n\"name\": \"{}\"", name);
fmt::print(os, ",\n\"id\": \"{}\"", prop);
fmt::print(os, ",\n\"type\": \"{}\"", static_cast<int>(kind));
fmt::print(os, ",\n\"min\": \"{}\"", source.GetPropertyMin(prop, &status));
fmt::print(os, ",\n\"max\": \"{}\"", source.GetPropertyMax(prop, &status));
fmt::print(os, ",\n\"step\": \"{}\"",
wpi::print(os, "\n\"name\": \"{}\"", name);
wpi::print(os, ",\n\"id\": \"{}\"", prop);
wpi::print(os, ",\n\"type\": \"{}\"", static_cast<int>(kind));
wpi::print(os, ",\n\"min\": \"{}\"", source.GetPropertyMin(prop, &status));
wpi::print(os, ",\n\"max\": \"{}\"", source.GetPropertyMax(prop, &status));
wpi::print(os, ",\n\"step\": \"{}\"",
source.GetPropertyStep(prop, &status));
fmt::print(os, ",\n\"default\": \"{}\"",
wpi::print(os, ",\n\"default\": \"{}\"",
source.GetPropertyDefault(prop, &status));
os << ",\n\"value\": \"";
switch (kind) {
case CS_PROP_BOOLEAN:
case CS_PROP_INTEGER:
case CS_PROP_ENUM:
fmt::print(os, "{}", source.GetProperty(prop, &status));
wpi::print(os, "{}", source.GetProperty(prop, &status));
break;
case CS_PROP_STRING: {
wpi::SmallString<128> strval_buf;
@@ -543,7 +544,7 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
for (char ch : *choice) {
ch_name.push_back(std::isprint(ch) ? ch : ' ');
}
fmt::print(os, "\"{}\": \"{}\"", j, ch_name.str());
wpi::print(os, "\"{}\": \"{}\"", j, ch_name.str());
}
os << "}\n";
}
@@ -585,9 +586,9 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
os << "unknown";
break;
}
fmt::print(os, "\",\n\"width\": \"{}\"", mode.width);
fmt::print(os, ",\n\"height\": \"{}\"", mode.height);
fmt::print(os, ",\n\"fps\": \"{}\"", mode.fps);
wpi::print(os, "\",\n\"width\": \"{}\"", mode.width);
wpi::print(os, ",\n\"height\": \"{}\"", mode.height);
wpi::print(os, ",\n\"fps\": \"{}\"", mode.fps);
os << '}';
}
os << "\n]\n}\n";
@@ -772,8 +773,8 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
header.clear();
oss << "\r\n--" BOUNDARY "\r\n"
<< "Content-Type: image/jpeg\r\n";
fmt::print(oss, "Content-Length: {}\r\n", size);
fmt::print(oss, "X-Timestamp: {}\r\n", timestamp);
wpi::print(oss, "Content-Length: {}\r\n", size);
wpi::print(oss, "X-Timestamp: {}\r\n", timestamp);
oss << "\r\n";
os << oss.str();
if (addDHT) {
@@ -878,8 +879,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
ProcessCommand(os, *source, parameters, true);
} else {
SendHeader(os, 200, "OK", "text/plain");
os << "Ignored due to no connected source."
<< "\r\n";
os << "Ignored due to no connected source." << "\r\n";
SDEBUG("Ignored due to no connected source.");
}
break;
@@ -1047,13 +1047,17 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
extern "C" {
CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
int port, CS_Status* status) {
return cs::CreateMjpegServer(name, listenAddress, port, status);
CS_Sink CS_CreateMjpegServer(const struct WPI_String* name,
const struct WPI_String* listenAddress, int port,
CS_Status* status) {
return cs::CreateMjpegServer(wpi::to_string_view(name),
wpi::to_string_view(listenAddress), port,
status);
}
char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
return ConvertToC(cs::GetMjpegServerListenAddress(sink, status));
void CS_GetMjpegServerListenAddress(CS_Sink sink, WPI_String* listenAddress,
CS_Status* status) {
cs::ConvertToC(listenAddress, cs::GetMjpegServerListenAddress(sink, status));
}
int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status) {

View File

@@ -143,25 +143,28 @@ void RawSinkImpl::ThreadMain() {
}
namespace cs {
CS_Sink CreateRawSink(std::string_view name, CS_Status* status) {
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(CS_SINK_RAW,
return inst.CreateSink(isCv ? CS_SINK_CV : CS_SINK_RAW,
std::make_shared<RawSinkImpl>(
name, inst.logger, inst.notifier, inst.telemetry));
}
CS_Sink CreateRawSinkCallback(std::string_view name,
CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSink(CS_SINK_RAW, std::make_shared<RawSinkImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, processFrame));
return inst.CreateSink(
isCv ? CS_SINK_CV : CS_SINK_RAW,
std::make_shared<RawSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, processFrame));
}
uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_RAW) {
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
@@ -171,25 +174,27 @@ uint64_t GrabSinkFrame(CS_Sink sink, WPI_RawFrame& image, CS_Status* status) {
uint64_t GrabSinkFrameTimeout(CS_Sink sink, WPI_RawFrame& image, double timeout,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_RAW) {
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return 0;
}
return static_cast<RawSinkImpl&>(*data->sink).GrabFrame(image, timeout);
}
} // namespace cs
extern "C" {
CS_Sink CS_CreateRawSink(const char* name, CS_Status* status) {
return cs::CreateRawSink(name, status);
CS_Sink CS_CreateRawSink(const struct WPI_String* name, CS_Bool isCv,
CS_Status* status) {
return cs::CreateRawSink(wpi::to_string_view(name), isCv, status);
}
CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
void (*processFrame)(void* data,
uint64_t time),
CS_Status* status) {
CS_Sink CS_CreateRawSinkCallback(
const struct WPI_String* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data, uint64_t time), CS_Status* status) {
return cs::CreateRawSinkCallback(
name, [=](uint64_t time) { processFrame(data, time); }, status);
wpi::to_string_view(name), isCv,
[=](uint64_t time) { processFrame(data, time); }, status);
}
uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* image,
@@ -201,4 +206,5 @@ uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* image,
double timeout, CS_Status* status) {
return cs::GrabSinkFrameTimeout(sink, *image, timeout, status);
}
} // extern "C"

View File

@@ -22,62 +22,46 @@ RawSourceImpl::RawSourceImpl(std::string_view name, wpi::Logger& logger,
RawSourceImpl::~RawSourceImpl() = default;
void RawSourceImpl::PutFrame(const WPI_RawFrame& image) {
int type;
switch (image.pixelFormat) {
case VideoMode::kYUYV:
case VideoMode::kRGB565:
case VideoMode::kY16:
case VideoMode::kUYVY:
type = CV_8UC2;
break;
case VideoMode::kBGR:
type = CV_8UC3;
break;
case VideoMode::kGray:
case VideoMode::kMJPEG:
default:
type = CV_8UC1;
break;
}
cv::Mat finalImage{image.height, image.width, type, image.data,
static_cast<size_t>(image.stride)};
std::unique_ptr<Image> dest =
AllocImage(static_cast<VideoMode::PixelFormat>(image.pixelFormat),
image.width, image.height, image.size);
finalImage.copyTo(dest->AsMat());
SourceImpl::PutFrame(std::move(dest), wpi::Now());
auto currentTime = wpi::Now();
std::string_view data_view{reinterpret_cast<char*>(image.data), image.size};
SourceImpl::PutFrame(static_cast<VideoMode::PixelFormat>(image.pixelFormat),
image.width, image.height, data_view, currentTime);
}
namespace cs {
CS_Source CreateRawSource(std::string_view name, const VideoMode& mode,
CS_Status* status) {
static constexpr unsigned SourceMask = CS_SOURCE_CV | CS_SOURCE_RAW;
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status) {
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_RAW, std::make_shared<RawSourceImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, mode));
return inst.CreateSource(
isCv ? CS_SOURCE_CV : CS_SOURCE_RAW,
std::make_shared<RawSourceImpl>(name, inst.logger, inst.notifier,
inst.telemetry, mode));
}
void PutSourceFrame(CS_Source source, const WPI_RawFrame& image,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_RAW) {
if (!data || (data->kind & SourceMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
static_cast<RawSourceImpl&>(*data->source).PutFrame(image);
}
} // namespace cs
extern "C" {
CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
CS_Status* status) {
return cs::CreateRawSource(name, static_cast<const cs::VideoMode&>(*mode),
status);
CS_Source CS_CreateRawSource(const struct WPI_String* name, CS_Bool isCv,
const CS_VideoMode* mode, CS_Status* status) {
return cs::CreateRawSource(wpi::to_string_view(name), isCv,
static_cast<const cs::VideoMode&>(*mode), status);
}
void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image,
CS_Status* status) {
return cs::PutSourceFrame(source, *image, status);
}
} // extern "C"

View File

@@ -4,11 +4,13 @@
#include "SinkImpl.h"
#include <wpi/SmallString.h>
#include <wpi/json.h>
#include "Instance.h"
#include "Notifier.h"
#include "SourceImpl.h"
#include "c_util.h"
using namespace cs;
@@ -195,3 +197,64 @@ void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
}
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}
namespace cs {
static constexpr unsigned SinkMask = CS_SINK_CV | CS_SINK_RAW;
void SetSinkDescription(CS_Sink sink, std::string_view description,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
data->sink->SetDescription(description);
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->sink->GetError();
}
std::string_view GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return {};
}
return data->sink->GetError(buf);
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data || (data->kind & SinkMask) == 0) {
*status = CS_INVALID_HANDLE;
return;
}
data->sink->SetEnabled(enabled);
}
} // namespace cs
extern "C" {
void CS_SetSinkDescription(CS_Sink sink, const struct WPI_String* description,
CS_Status* status) {
return cs::SetSinkDescription(sink, wpi::to_string_view(description), status);
}
void CS_GetSinkError(CS_Sink sink, struct WPI_String* error,
CS_Status* status) {
wpi::SmallString<128> buf;
cs::ConvertToC(error, cs::GetSinkError(sink, buf, status));
}
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status) {
return cs::SetSinkEnabled(sink, enabled, status);
}
} // extern "C"

View File

@@ -196,6 +196,8 @@ bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
mode.pixelFormat = cs::VideoMode::kRGB565;
} else if (wpi::equals_lower(str, "bgr")) {
mode.pixelFormat = cs::VideoMode::kBGR;
} else if (wpi::equals_lower(str, "bgra")) {
mode.pixelFormat = cs::VideoMode::kBGRA;
} else if (wpi::equals_lower(str, "gray")) {
mode.pixelFormat = cs::VideoMode::kGray;
} else if (wpi::equals_lower(str, "y16")) {
@@ -361,6 +363,9 @@ wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
case VideoMode::kBGR:
pixelFormat = "bgr";
break;
case VideoMode::kBGRA:
pixelFormat = "bgra";
break;
case VideoMode::kGray:
pixelFormat = "gray";
break;
@@ -450,6 +455,15 @@ std::unique_ptr<Image> SourceImpl::AllocImage(
void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
int height, std::string_view data, Frame::Time time) {
if (pixelFormat == VideoMode::PixelFormat::kBGRA) {
// Write BGRA as BGR to save a copy
auto image =
CreateImageFromBGRA(this, width, height, width * 4,
reinterpret_cast<const uint8_t*>(data.data()));
PutFrame(std::move(image), time);
return;
}
auto image = AllocImage(pixelFormat, width, height, data.size());
// Copy in image data

View File

@@ -4,6 +4,8 @@
#include "cscore_c.h" // NOLINT(build/include_order)
#include <wpi/MemAlloc.h>
#include "c_util.h"
#include "cscore_cpp.h"
@@ -11,56 +13,53 @@ using namespace cs;
static void ConvertToC(CS_UsbCameraInfo* out, const UsbCameraInfo& in) {
out->dev = in.dev;
out->path = ConvertToC(in.path);
out->name = ConvertToC(in.name);
out->otherPaths = static_cast<char**>(
wpi::safe_malloc(in.otherPaths.size() * sizeof(char*)));
cs::ConvertToC(&out->path, in.path);
cs::ConvertToC(&out->name, in.name);
out->otherPaths = WPI_AllocateStringArray(in.otherPaths.size());
out->otherPathsCount = in.otherPaths.size();
for (size_t i = 0; i < in.otherPaths.size(); ++i) {
out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
cs::ConvertToC(&out->otherPaths[i], in.otherPaths[i]);
}
out->vendorId = in.vendorId;
out->productId = in.productId;
}
static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
std::free(info->path);
std::free(info->name);
WPI_FreeString(&info->path);
WPI_FreeString(&info->name);
for (int i = 0; i < info->otherPathsCount; ++i) {
std::free(info->otherPaths[i]);
WPI_FreeString(&info->otherPaths[i]);
}
std::free(info->otherPaths);
}
extern "C" {
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
return cs::CreateUsbCameraDev(name, dev, status);
CS_Source CS_CreateUsbCameraDev(const struct WPI_String* name, int dev,
CS_Status* status) {
return cs::CreateUsbCameraDev(wpi::to_string_view(name), dev, status);
}
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Source CS_CreateUsbCameraPath(const struct WPI_String* name,
const struct WPI_String* path,
CS_Status* status) {
return cs::CreateUsbCameraPath(name, path, status);
return cs::CreateUsbCameraPath(wpi::to_string_view(name),
wpi::to_string_view(path), status);
}
void CS_SetUsbCameraPath(CS_Source source, const char* path,
void CS_SetUsbCameraPath(CS_Source source, const struct WPI_String* path,
CS_Status* status) {
cs::SetUsbCameraPath(source, path, status);
cs::SetUsbCameraPath(source, wpi::to_string_view(path), status);
}
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(cs::GetUsbCameraPath(source, status));
void CS_GetUsbCameraPath(CS_Source source, WPI_String* path,
CS_Status* status) {
ConvertToC(path, cs::GetUsbCameraPath(source, status));
}
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status) {
auto info = cs::GetUsbCameraInfo(source, status);
if (*status != CS_OK) {
return nullptr;
}
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
wpi::safe_malloc(sizeof(CS_UsbCameraInfo)));
ConvertToC(out, info);
return out;
void CS_GetUsbCameraInfo(CS_Source source, CS_UsbCameraInfo* info,
CS_Status* status) {
auto info_cpp = cs::GetUsbCameraInfo(source, status);
ConvertToC(info, info_cpp);
}
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
@@ -89,7 +88,6 @@ void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
return;
}
FreeUsbCameraInfo(info);
std::free(info);
}
} // extern "C"

View File

@@ -5,19 +5,16 @@
#ifndef CSCORE_C_UTIL_H_
#define CSCORE_C_UTIL_H_
#include <cstdlib>
#include <cstring>
#include <string_view>
#include <wpi/MemAlloc.h>
#include <wpi/string.h>
namespace cs {
inline char* ConvertToC(std::string_view in) {
char* out = static_cast<char*>(wpi::safe_malloc(in.size() + 1));
std::memmove(out, in.data(), in.size());
out[in.size()] = '\0';
return out;
inline void ConvertToC(struct WPI_String* output, std::string_view str) {
char* write = WPI_AllocateString(output, str.size());
std::memcpy(write, str.data(), str.size());
}
} // namespace cs

View File

@@ -7,7 +7,6 @@
#include <cstddef>
#include <cstdlib>
#include <opencv2/core/core.hpp>
#include <wpi/MemAlloc.h>
#include <wpi/SmallString.h>
@@ -20,12 +19,12 @@ static CS_Event ConvertToC(const cs::RawEvent& rawEvent) {
event.kind = static_cast<CS_EventKind>(static_cast<int>(rawEvent.kind));
event.source = rawEvent.sourceHandle;
event.sink = rawEvent.sinkHandle;
event.name = rawEvent.name.c_str();
cs::ConvertToC(&event.name, rawEvent.name);
event.mode = rawEvent.mode;
event.property = rawEvent.propertyHandle;
event.propertyKind = rawEvent.propertyKind;
event.value = rawEvent.value;
event.valueStr = rawEvent.valueStr.c_str();
cs::ConvertToC(&event.name, rawEvent.valueStr);
event.listener = rawEvent.listener;
return event;
}
@@ -54,13 +53,10 @@ CS_PropertyKind CS_GetPropertyKind(CS_Property property, CS_Status* status) {
return cs::GetPropertyKind(property, status);
}
char* CS_GetPropertyName(CS_Property property, CS_Status* status) {
void CS_GetPropertyName(CS_Property property, WPI_String* name,
CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetPropertyName(property, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(name, cs::GetPropertyName(property, buf, status));
}
int CS_GetProperty(CS_Property property, CS_Status* status) {
@@ -87,28 +83,24 @@ int CS_GetPropertyDefault(CS_Property property, CS_Status* status) {
return cs::GetPropertyDefault(property, status);
}
char* CS_GetStringProperty(CS_Property property, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetStringProperty(property, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
}
void CS_SetStringProperty(CS_Property property, const char* value,
void CS_GetStringProperty(CS_Property property, WPI_String* value,
CS_Status* status) {
return cs::SetStringProperty(property, value, status);
wpi::SmallString<128> buf;
cs::ConvertToC(value, cs::GetStringProperty(property, buf, status));
}
char** CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status) {
void CS_SetStringProperty(CS_Property property, const struct WPI_String* value,
CS_Status* status) {
return cs::SetStringProperty(property, wpi::to_string_view(value), status);
}
WPI_String* CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status) {
auto choices = cs::GetEnumPropertyChoices(property, status);
char** out =
static_cast<char**>(wpi::safe_malloc(choices.size() * sizeof(char*)));
WPI_String* out = WPI_AllocateStringArray(choices.size());
*count = choices.size();
for (size_t i = 0; i < choices.size(); ++i) {
out[i] = cs::ConvertToC(choices[i]);
cs::ConvertToC(&out[i], choices[i]);
}
return out;
}
@@ -117,22 +109,15 @@ CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status) {
return cs::GetSourceKind(source, status);
}
char* CS_GetSourceName(CS_Source source, CS_Status* status) {
void CS_GetSourceName(CS_Source source, WPI_String* name, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSourceName(source, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(name, cs::GetSourceName(source, buf, status));
}
char* CS_GetSourceDescription(CS_Source source, CS_Status* status) {
void CS_GetSourceDescription(CS_Source source, WPI_String* description,
CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSourceDescription(source, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(description, cs::GetSourceDescription(source, buf, status));
}
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
@@ -153,9 +138,10 @@ CS_Bool CS_IsSourceEnabled(CS_Source source, CS_Status* status) {
return cs::IsSourceEnabled(source, status);
}
CS_Property CS_GetSourceProperty(CS_Source source, const char* name,
CS_Property CS_GetSourceProperty(CS_Source source,
const struct WPI_String* name,
CS_Status* status) {
return cs::GetSourceProperty(source, name, status);
return cs::GetSourceProperty(source, wpi::to_string_view(name), status);
}
CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count,
@@ -210,13 +196,15 @@ CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
return cs::SetSourceFPS(source, fps, status);
}
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
CS_Bool CS_SetSourceConfigJson(CS_Source source,
const struct WPI_String* config,
CS_Status* status) {
return cs::SetSourceConfigJson(source, config, status);
return cs::SetSourceConfigJson(source, wpi::to_string_view(config), status);
}
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) {
return cs::ConvertToC(cs::GetSourceConfigJson(source, status));
void CS_GetSourceConfigJson(CS_Source source, WPI_String* config,
CS_Status* status) {
cs::ConvertToC(config, cs::GetSourceConfigJson(source, status));
}
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
@@ -287,27 +275,20 @@ CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status) {
return cs::GetSinkKind(sink, status);
}
char* CS_GetSinkName(CS_Sink sink, CS_Status* status) {
void CS_GetSinkName(CS_Sink sink, WPI_String* name, CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkName(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(name, cs::GetSinkName(sink, buf, status));
}
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status) {
void CS_GetSinkDescription(CS_Sink sink, WPI_String* description,
CS_Status* status) {
wpi::SmallString<128> buf;
auto str = cs::GetSinkDescription(sink, buf, status);
if (*status != 0) {
return nullptr;
}
return cs::ConvertToC(str);
cs::ConvertToC(description, cs::GetSinkDescription(sink, buf, status));
}
CS_Property CS_GetSinkProperty(CS_Sink sink, const char* name,
CS_Property CS_GetSinkProperty(CS_Sink sink, const struct WPI_String* name,
CS_Status* status) {
return cs::GetSinkProperty(sink, name, status);
return cs::GetSinkProperty(sink, wpi::to_string_view(name), status);
}
CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
@@ -321,13 +302,13 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
return out;
}
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const struct WPI_String* config,
CS_Status* status) {
return cs::SetSinkConfigJson(sink, config, status);
return cs::SetSinkConfigJson(sink, wpi::to_string_view(config), status);
}
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
return cs::ConvertToC(cs::GetSinkConfigJson(sink, status));
void CS_GetSinkConfigJson(CS_Sink sink, WPI_String* config, CS_Status* status) {
cs::ConvertToC(config, cs::GetSinkConfigJson(sink, status));
}
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
@@ -338,9 +319,10 @@ CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status) {
return cs::GetSinkSource(sink, status);
}
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
CS_Property CS_GetSinkSourceProperty(CS_Sink sink,
const struct WPI_String* name,
CS_Status* status) {
return cs::GetSinkSourceProperty(sink, name, status);
return cs::GetSinkSourceProperty(sink, wpi::to_string_view(name), status);
}
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status) {
@@ -361,7 +343,7 @@ void CS_SetListenerOnExit(void (*onExit)(void* data), void* data) {
CS_Listener CS_AddListener(void* data,
void (*callback)(void* data, const CS_Event* event),
int eventMask, int immediateNotify,
int eventMask, CS_Bool immediateNotify,
CS_Status* status) {
return cs::AddListener(
[=](const cs::RawEvent& rawEvent) {
@@ -436,8 +418,15 @@ double CS_GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
return cs::GetTelemetryAverageValue(handle, kind, status);
}
void CS_SetLogger(CS_LogFunc func, unsigned int min_level) {
cs::SetLogger(func, min_level);
void CS_SetLogger(CS_LogFunc func, void* data, unsigned int min_level) {
cs::SetLogger(
[=](unsigned int level, const char* file, unsigned int line,
const char* msg) {
auto fileStr = wpi::make_string(file);
auto msgStr = wpi::make_string(msg);
func(data, level, &fileStr, line, &msgStr);
},
min_level);
}
void CS_SetDefaultLogger(unsigned int min_level) {
@@ -494,20 +483,6 @@ void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count) {
std::free(sinks);
}
void CS_FreeString(char* str) {
std::free(str);
}
void CS_FreeEnumPropertyChoices(char** choices, int count) {
if (!choices) {
return;
}
for (int i = 0; i < count; ++i) {
std::free(choices[i]);
}
std::free(choices);
}
void CS_FreeEnumeratedProperties(CS_Property* properties, int count) {
std::free(properties);
}
@@ -516,29 +491,18 @@ void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count) {
std::free(modes);
}
char* CS_GetHostname(void) {
return cs::ConvertToC(cs::GetHostname());
void CS_GetHostname(struct WPI_String* hostname) {
cs::ConvertToC(hostname, cs::GetHostname());
}
char** CS_GetNetworkInterfaces(int* count) {
WPI_String* CS_GetNetworkInterfaces(int* count) {
auto interfaces = cs::GetNetworkInterfaces();
char** out =
static_cast<char**>(wpi::safe_malloc(interfaces.size() * sizeof(char*)));
WPI_String* out = WPI_AllocateStringArray(interfaces.size());
*count = interfaces.size();
for (size_t i = 0; i < interfaces.size(); ++i) {
out[i] = cs::ConvertToC(interfaces[i]);
cs::ConvertToC(&out[i], interfaces[i]);
}
return out;
}
void CS_FreeNetworkInterfaces(char** interfaces, int count) {
if (!interfaces) {
return;
}
for (int i = 0; i < count; ++i) {
std::free(interfaces[i]);
}
std::free(interfaces);
}
} // extern "C"

View File

@@ -6,14 +6,12 @@
#include <span>
#include <fmt/format.h>
#include <opencv2/core/core.hpp>
#define WPI_RAWFRAME_JNI
#include <wpi/RawFrame.h>
#include <wpi/SmallString.h>
#include <wpi/jni_util.h>
#include "cscore_cv.h"
#include "cscore_raw.h"
#include "cscore_runloop.h"
#include "edu_wpi_first_cscore_CameraServerJNI.h"
@@ -82,32 +80,6 @@ static void ListenerOnExit() {
jvm->DetachCurrentThread();
}
/// throw java exception
static void ThrowJavaException(JNIEnv* env, const std::exception* e) {
wpi::SmallString<128> what;
jclass je = nullptr;
if (e) {
const char* exception_type = "std::exception";
if (dynamic_cast<const cv::Exception*>(e)) {
exception_type = "cv::Exception";
je = env->FindClass("org/opencv/core/CvException");
}
what = exception_type;
what += ": ";
what += e->what();
} else {
what = "unknown exception";
}
if (!je) {
je = exceptionEx;
}
env->ThrowNew(je, what.c_str());
}
extern "C" {
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
@@ -593,40 +565,15 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createHttpCameraMulti
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: createCvSource
* Signature: (Ljava/lang/String;IIII)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSource
(JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
jint fps)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateCvSource(
JStringRef{env, name}.str(),
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
&status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: createRawSource
* Signature: (Ljava/lang/String;IIII)I
* Signature: (Ljava/lang/String;ZIIII)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource
(JNIEnv* env, jclass, jstring name, jint pixelFormat, jint width, jint height,
jint fps)
(JNIEnv* env, jclass, jstring name, jboolean isCv, jint pixelFormat,
jint width, jint height, jint fps)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
@@ -634,7 +581,7 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createRawSource
}
CS_Status status = 0;
auto val = cs::CreateRawSource(
JStringRef{env, name}.str(),
JStringRef{env, name}.str(), isCv,
cs::VideoMode{static_cast<cs::VideoMode::PixelFormat>(pixelFormat),
static_cast<int>(width), static_cast<int>(height),
static_cast<int>(fps)},
@@ -1202,27 +1149,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_getHttpCameraUrls
return MakeJStringArray(env, arr);
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: putSourceFrame
* Signature: (IJ)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_putSourceFrame
(JNIEnv* env, jclass, jint source, jlong imageNativeObj)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
cs::PutSourceFrame(source, image, &status);
CheckStatus(env, status);
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
} catch (...) {
ThrowJavaException(env, nullptr);
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: putRawSourceFrame
@@ -1421,42 +1347,21 @@ Java_edu_wpi_first_cscore_CameraServerJNI_createMjpegServer
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: createCvSink
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_createCvSink
(JNIEnv* env, jclass, jstring name, jint pixelFormat)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateCvSink(
JStringRef{env, name}.str(),
static_cast<cs::VideoMode::PixelFormat>(pixelFormat), &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: createRawSink
* Signature: (Ljava/lang/String;)I
* Signature: (Ljava/lang/String;Z)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_cscore_CameraServerJNI_createRawSink
(JNIEnv* env, jclass, jstring name)
(JNIEnv* env, jclass, jstring name, jboolean isCv)
{
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
}
CS_Status status = 0;
auto val = cs::CreateRawSink(JStringRef{env, name}.str(), &status);
auto val = cs::CreateRawSink(JStringRef{env, name}.str(), isCv, &status);
CheckStatus(env, status);
return val;
}
@@ -1707,54 +1612,6 @@ Java_edu_wpi_first_cscore_CameraServerJNI_setSinkDescription
CheckStatus(env, status);
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: grabSinkFrame
* Signature: (IJ)J
*/
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrame
(JNIEnv* env, jclass, jint sink, jlong imageNativeObj)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
auto rv = cs::GrabSinkFrame(sink, image, &status);
CheckStatus(env, status);
return rv;
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
return 0;
} catch (...) {
ThrowJavaException(env, nullptr);
return 0;
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerCvJNI
* Method: grabSinkFrameTimeout
* Signature: (IJD)J
*/
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_cscore_CameraServerCvJNI_grabSinkFrameTimeout
(JNIEnv* env, jclass, jint sink, jlong imageNativeObj, jdouble timeout)
{
try {
cv::Mat& image = *((cv::Mat*)imageNativeObj);
CS_Status status = 0;
auto rv = cs::GrabSinkFrameTimeout(sink, image, timeout, &status);
CheckStatus(env, status);
return rv;
} catch (const std::exception& e) {
ThrowJavaException(env, &e);
return 0;
} catch (...) {
ThrowJavaException(env, nullptr);
return 0;
}
}
/*
* Class: edu_wpi_first_cscore_CameraServerJNI
* Method: grabRawSinkFrame

View File

@@ -8,6 +8,7 @@
#include <stdint.h>
#include <wpi/RawFrame.h>
#include <wpi/string.h>
#ifdef __cplusplus
#include <cstddef>
@@ -204,7 +205,7 @@ struct CS_Event {
CS_Sink sink;
// Source/sink/property name
const char* name;
struct WPI_String name;
// Fields for CS_SOURCE_VIDEOMODE_CHANGED event
CS_VideoMode mode;
@@ -213,7 +214,7 @@ struct CS_Event {
CS_Property property;
enum CS_PropertyKind propertyKind;
int value;
const char* valueStr;
struct WPI_String valueStr;
// Listener that was triggered
CS_Listener listener;
@@ -224,10 +225,10 @@ struct CS_Event {
*/
typedef struct CS_UsbCameraInfo {
int dev;
char* path;
char* name;
struct WPI_String path;
struct WPI_String name;
int otherPathsCount;
char** otherPaths;
struct WPI_String* otherPaths;
int vendorId;
int productId;
} CS_UsbCameraInfo;
@@ -238,34 +239,40 @@ typedef struct CS_UsbCameraInfo {
*/
enum CS_PropertyKind CS_GetPropertyKind(CS_Property property,
CS_Status* status);
char* CS_GetPropertyName(CS_Property property, CS_Status* status);
void CS_GetPropertyName(CS_Property property, struct WPI_String* name,
CS_Status* status);
int CS_GetProperty(CS_Property property, CS_Status* status);
void CS_SetProperty(CS_Property property, int value, CS_Status* status);
int CS_GetPropertyMin(CS_Property property, CS_Status* status);
int CS_GetPropertyMax(CS_Property property, CS_Status* status);
int CS_GetPropertyStep(CS_Property property, CS_Status* status);
int CS_GetPropertyDefault(CS_Property property, CS_Status* status);
char* CS_GetStringProperty(CS_Property property, CS_Status* status);
void CS_SetStringProperty(CS_Property property, const char* value,
void CS_GetStringProperty(CS_Property property, struct WPI_String* value,
CS_Status* status);
char** CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status);
void CS_SetStringProperty(CS_Property property, const struct WPI_String* value,
CS_Status* status);
struct WPI_String* CS_GetEnumPropertyChoices(CS_Property property, int* count,
CS_Status* status);
/** @} */
/**
* @defgroup cscore_source_create_cfunc Source Creation Functions
* @{
*/
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status);
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Source CS_CreateUsbCameraDev(const struct WPI_String* name, int dev,
CS_Status* status);
CS_Source CS_CreateUsbCameraPath(const struct WPI_String* name,
const struct WPI_String* path,
CS_Status* status);
CS_Source CS_CreateHttpCamera(const char* name, const char* url,
CS_Source CS_CreateHttpCamera(const struct WPI_String* name,
const struct WPI_String* url,
enum CS_HttpCameraKind kind, CS_Status* status);
CS_Source CS_CreateHttpCameraMulti(const char* name, const char** urls,
int count, enum CS_HttpCameraKind kind,
CS_Source CS_CreateHttpCameraMulti(const struct WPI_String* name,
const struct WPI_String* urls, int count,
enum CS_HttpCameraKind kind,
CS_Status* status);
CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
CS_Status* status);
CS_Source CS_CreateCvSource(const struct WPI_String* name,
const CS_VideoMode* mode, CS_Status* status);
/** @} */
/**
@@ -273,15 +280,18 @@ CS_Source CS_CreateCvSource(const char* name, const CS_VideoMode* mode,
* @{
*/
enum CS_SourceKind CS_GetSourceKind(CS_Source source, CS_Status* status);
char* CS_GetSourceName(CS_Source source, CS_Status* status);
char* CS_GetSourceDescription(CS_Source source, CS_Status* status);
void CS_GetSourceName(CS_Source source, struct WPI_String* name,
CS_Status* status);
void CS_GetSourceDescription(CS_Source source, struct WPI_String* description,
CS_Status* status);
uint64_t CS_GetSourceLastFrameTime(CS_Source source, CS_Status* status);
void CS_SetSourceConnectionStrategy(CS_Source source,
enum CS_ConnectionStrategy strategy,
CS_Status* status);
CS_Bool CS_IsSourceConnected(CS_Source source, CS_Status* status);
CS_Bool CS_IsSourceEnabled(CS_Source source, CS_Status* status);
CS_Property CS_GetSourceProperty(CS_Source source, const char* name,
CS_Property CS_GetSourceProperty(CS_Source source,
const struct WPI_String* name,
CS_Status* status);
CS_Property* CS_EnumerateSourceProperties(CS_Source source, int* count,
CS_Status* status);
@@ -299,9 +309,11 @@ CS_Bool CS_SetSourcePixelFormat(CS_Source source,
CS_Bool CS_SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status);
CS_Bool CS_SetSourceFPS(CS_Source source, int fps, CS_Status* status);
CS_Bool CS_SetSourceConfigJson(CS_Source source, const char* config,
CS_Bool CS_SetSourceConfigJson(CS_Source source,
const struct WPI_String* config,
CS_Status* status);
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status);
void CS_GetSourceConfigJson(CS_Source source, struct WPI_String* config,
CS_Status* status);
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status);
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
@@ -330,9 +342,12 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @defgroup cscore_usbcamera_cfunc UsbCamera Source Functions
* @{
*/
void CS_SetUsbCameraPath(CS_Source source, const char* path, CS_Status* status);
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
void CS_SetUsbCameraPath(CS_Source source, const struct WPI_String* path,
CS_Status* status);
void CS_GetUsbCameraPath(CS_Source source, struct WPI_String* path,
CS_Status* status);
void CS_GetUsbCameraInfo(CS_Source source, CS_UsbCameraInfo* info,
CS_Status* status);
/** @} */
/**
@@ -341,38 +356,43 @@ CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
*/
enum CS_HttpCameraKind CS_GetHttpCameraKind(CS_Source source,
CS_Status* status);
void CS_SetHttpCameraUrls(CS_Source source, const char** urls, int count,
CS_Status* status);
char** CS_GetHttpCameraUrls(CS_Source source, int* count, CS_Status* status);
void CS_SetHttpCameraUrls(CS_Source source, const struct WPI_String* urls,
int count, CS_Status* status);
struct WPI_String* CS_GetHttpCameraUrls(CS_Source source, int* count,
CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_source_cfunc OpenCV Source Functions
* @defgroup cscore_frame_source_cfunc Frame Source Functions
* @{
*/
void CS_NotifySourceError(CS_Source source, const char* msg, CS_Status* status);
void CS_NotifySourceError(CS_Source source, const struct WPI_String* msg,
CS_Status* status);
void CS_SetSourceConnected(CS_Source source, CS_Bool connected,
CS_Status* status);
void CS_SetSourceDescription(CS_Source source, const char* description,
void CS_SetSourceDescription(CS_Source source,
const struct WPI_String* description,
CS_Status* status);
CS_Property CS_CreateSourceProperty(CS_Source source, const char* name,
CS_Property CS_CreateSourceProperty(CS_Source source,
const struct WPI_String* name,
enum CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue,
int value, CS_Status* status);
void CS_SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
const char** choices, int count,
CS_Status* status);
const struct WPI_String* choices,
int count, CS_Status* status);
/** @} */
/**
* @defgroup cscore_sink_create_cfunc Sink Creation Functions
* @{
*/
CS_Sink CS_CreateMjpegServer(const char* name, const char* listenAddress,
int port, CS_Status* status);
CS_Sink CS_CreateCvSink(const char* name, enum WPI_PixelFormat pixelFormat,
CS_Status* status);
CS_Sink CS_CreateCvSinkCallback(const char* name,
CS_Sink CS_CreateMjpegServer(const struct WPI_String* name,
const struct WPI_String* listenAddress, int port,
CS_Status* status);
CS_Sink CS_CreateCvSink(const struct WPI_String* name,
enum WPI_PixelFormat pixelFormat, CS_Status* status);
CS_Sink CS_CreateCvSinkCallback(const struct WPI_String* name,
enum WPI_PixelFormat pixelFormat, void* data,
void (*processFrame)(void* data, uint64_t time),
CS_Status* status);
@@ -383,18 +403,21 @@ CS_Sink CS_CreateCvSinkCallback(const char* name,
* @{
*/
enum CS_SinkKind CS_GetSinkKind(CS_Sink sink, CS_Status* status);
char* CS_GetSinkName(CS_Sink sink, CS_Status* status);
char* CS_GetSinkDescription(CS_Sink sink, CS_Status* status);
CS_Property CS_GetSinkProperty(CS_Sink sink, const char* name,
void CS_GetSinkName(CS_Sink sink, struct WPI_String* name, CS_Status* status);
void CS_GetSinkDescription(CS_Sink sink, struct WPI_String* description,
CS_Status* status);
CS_Property CS_GetSinkProperty(CS_Sink sink, const struct WPI_String* name,
CS_Status* status);
CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
CS_Status* status);
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
CS_Property CS_GetSinkSourceProperty(CS_Sink sink,
const struct WPI_String* name,
CS_Status* status);
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const struct WPI_String* config,
CS_Status* status);
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status);
void CS_GetSinkConfigJson(CS_Sink sink, struct WPI_String* config,
CS_Status* status);
CS_Source CS_GetSinkSource(CS_Sink sink, CS_Status* status);
CS_Sink CS_CopySink(CS_Sink sink, CS_Status* status);
void CS_ReleaseSink(CS_Sink sink, CS_Status* status);
@@ -404,17 +427,19 @@ void CS_ReleaseSink(CS_Sink sink, CS_Status* status);
* @defgroup cscore_mjpegserver_cfunc MjpegServer Sink Functions
* @{
*/
char* CS_GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status);
void CS_GetMjpegServerListenAddress(CS_Sink sink,
struct WPI_String* listenAddress,
CS_Status* status);
int CS_GetMjpegServerPort(CS_Sink sink, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_sink_cfunc OpenCV Sink Functions
* @defgroup cscore_frame_sink_cfunc Frame Sink Functions
* @{
*/
void CS_SetSinkDescription(CS_Sink sink, const char* description,
void CS_SetSinkDescription(CS_Sink sink, const struct WPI_String* description,
CS_Status* status);
char* CS_GetSinkError(CS_Sink sink, CS_Status* status);
void CS_GetSinkError(CS_Sink sink, struct WPI_String* error, CS_Status* status);
void CS_SetSinkEnabled(CS_Sink sink, CS_Bool enabled, CS_Status* status);
/** @} */
@@ -459,9 +484,10 @@ double CS_GetTelemetryAverageValue(CS_Handle handle, enum CS_TelemetryKind kind,
* @defgroup cscore_logging_cfunc Logging Functions
* @{
*/
typedef void (*CS_LogFunc)(unsigned int level, const char* file,
unsigned int line, const char* msg);
void CS_SetLogger(CS_LogFunc func, unsigned int min_level);
typedef void (*CS_LogFunc)(void* data, unsigned int level,
const struct WPI_String* file, unsigned int line,
const struct WPI_String* msg);
void CS_SetLogger(CS_LogFunc func, void* data, unsigned int min_level);
void CS_SetDefaultLogger(unsigned int min_level);
/** @} */
@@ -486,18 +512,14 @@ void CS_ReleaseEnumeratedSources(CS_Source* sources, int count);
CS_Sink* CS_EnumerateSinks(int* count, CS_Status* status);
void CS_ReleaseEnumeratedSinks(CS_Sink* sinks, int count);
void CS_FreeString(char* str);
void CS_FreeEnumPropertyChoices(char** choices, int count);
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info);
void CS_FreeHttpCameraUrls(char** urls, int count);
void CS_FreeEnumeratedProperties(CS_Property* properties, int count);
void CS_FreeEnumeratedVideoModes(CS_VideoMode* modes, int count);
char* CS_GetHostname(void);
void CS_GetHostname(struct WPI_String* hostName);
char** CS_GetNetworkInterfaces(int* count);
void CS_FreeNetworkInterfaces(char** interfaces, int count);
struct WPI_String* CS_GetNetworkInterfaces(int* count);
/** @} */
/** @} */

View File

@@ -68,7 +68,8 @@ struct VideoMode : public CS_VideoMode {
kBGR = WPI_PIXFMT_BGR,
kGray = WPI_PIXFMT_GRAY,
kY16 = WPI_PIXFMT_Y16,
kUYVY = WPI_PIXFMT_UYVY
kUYVY = WPI_PIXFMT_UYVY,
kBGRA = WPI_PIXFMT_BGRA,
};
VideoMode() {
pixelFormat = 0;
@@ -294,7 +295,7 @@ std::vector<std::string> GetHttpCameraUrls(CS_Source source, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_source_func OpenCV Source Functions
* @defgroup cscore_frame_source_func Frame Source Functions
* @{
*/
void NotifySourceError(CS_Source source, std::string_view msg,
@@ -365,7 +366,7 @@ int GetMjpegServerPort(CS_Sink sink, CS_Status* status);
/** @} */
/**
* @defgroup cscore_opencv_sink_func OpenCV Sink Functions
* @defgroup cscore_frame_sink_func Frame Sink Functions
* @{
*/
void SetSinkDescription(CS_Sink sink, std::string_view description,

View File

@@ -7,72 +7,17 @@
#include <functional>
#include "cscore_c.h"
#ifdef CSCORE_CSCORE_RAW_CV_H_
#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
#endif
#ifdef __cplusplus
#include "cscore_oo.h" // NOLINT(build/include_order)
#endif
#if CV_VERSION_MAJOR < 4
#ifdef __cplusplus
extern "C" { // NOLINT(build/include_order)
#endif
struct CvMat;
void CS_PutSourceFrame(CS_Source source, struct CvMat* image,
CS_Status* status);
uint64_t CS_GrabSinkFrame(CS_Sink sink, struct CvMat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeout(CS_Sink sink, struct CvMat* image,
double timeout, CS_Status* status);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // CV_VERSION_MAJOR < 4
#ifdef __cplusplus
#include <opencv2/core/mat.hpp>
#include "cscore_oo.h"
namespace cv {
class Mat;
} // namespace cv
#include "cscore_raw.h"
namespace cs {
/**
* @defgroup cscore_cpp_opencv_special cscore C functions taking a cv::Mat*
*
* These are needed for specific interop implementations.
* @{
*/
extern "C" {
uint64_t CS_GrabSinkFrameCpp(CS_Sink sink, cv::Mat* image, CS_Status* status);
uint64_t CS_GrabSinkFrameTimeoutCpp(CS_Sink sink, cv::Mat* image,
double timeout, CS_Status* status);
void CS_PutSourceFrameCpp(CS_Source source, cv::Mat* image, CS_Status* status);
} // extern "C"
/** @} */
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status);
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status);
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status);
/**
* A source for user code to provide OpenCV images as video frames.
* These sources require the WPILib OpenCV builds.
* For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
* include your Mat header before that header.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of most versions of OpenCV.
*/
class CvSource : public ImageSource {
public:
@@ -87,7 +32,7 @@ class CvSource : public ImageSource {
CvSource(std::string_view name, const VideoMode& mode);
/**
* Create an OpenCV source.
* Create an OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
@@ -99,26 +44,48 @@ class CvSource : public ImageSource {
int height, int fps);
/**
* Put an OpenCV image and notify sinks.
* Put an OpenCV image and notify sinks
*
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
* are supported. If the format, depth or channel order is different, use
* cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
* <p>
* The image format is guessed from the number of channels. The channel
* mapping is as follows. 1: kGray 2: kYUYV 3: BGR 4: BGRA Any other channel
* numbers will throw an error. If your image is an in alternate format, use
* the overload that takes a PixelFormat.
*
* @param image OpenCV image
* @param image OpenCV Image
*/
void PutFrame(cv::Mat& image);
/**
* Put an OpenCV image and notify sinks.
*
* <p>
* The format of the Mat must match the PixelFormat. You will corrupt memory
* if they dont. With skipVerification false, we will verify the number of
* channels matches the pixel format. If skipVerification is true, this step
* is skipped and is passed straight through.
*
* @param image OpenCV image
* @param pixelFormat The pixel format of the image
* @param skipVerification skip verifying pixel format
*/
void PutFrame(cv::Mat& image, VideoMode::PixelFormat pixelFormat,
bool skipVerification);
private:
static bool VerifyFormat(cv::Mat& image, VideoMode::PixelFormat pixelFormat);
};
/**
* A sink for user code to accept video frames as OpenCV images.
* These sinks require the WPILib OpenCV builds.
* For an alternate OpenCV, include "cscore_raw_cv.h" instead, and
* include your Mat header before that header.
* A source for user code to accept video frames as OpenCV images.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of most versions of OpenCV.
*/
class CvSink : public ImageSink {
public:
CvSink() = default;
CvSink(const CvSink& sink);
/**
* Create a sink for accepting OpenCV images.
@@ -127,89 +94,277 @@ class CvSink : public ImageSink {
* image.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Source pixel format
* @param pixelFormat The pixel format to read
*/
explicit CvSink(std::string_view name, VideoMode::PixelFormat pixelFormat =
VideoMode::PixelFormat::kBGR);
/**
* Create a sink for accepting OpenCV images in a separate thread.
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have the pixelFormat this class was constructed
* with.
*
* <p>A thread will be created that calls WaitForFrame() and calls the
* processFrame() callback each time a new frame arrives.
*
* @param name Source name (arbitrary unique identifier)
* @param processFrame Frame processing function; will be called with a
* time=0 if an error occurred. processFrame should call GetImage()
* or GetError() as needed, but should not call (except in very
* unusual circumstances) WaitForImage().
* @param pixelFormat Source pixel format
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
CvSink(std::string_view name, std::function<void(uint64_t time)> processFrame,
VideoMode::PixelFormat pixelFormat = VideoMode::PixelFormat::kBGR);
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have the pixelFormat this class was constructed
* with.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
* The provided image will have the pixelFormat this class was constructed
* with. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225) const;
uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
* The provided image will have the pixelFormat this class was constructed
* with. The data is backed by data in the CvSink. It will be invalidated by
* any grabFrame*() call on the sink.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image) const;
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
private:
constexpr int GetCvFormat(WPI_PixelFormat pixelFormat);
wpi::RawFrame rawFrame;
VideoMode::PixelFormat pixelFormat;
};
inline CvSource::CvSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateCvSource(name, mode, &m_status);
m_handle = CreateRawSource(name, true, mode, &m_status);
}
inline CvSource::CvSource(std::string_view name, VideoMode::PixelFormat format,
int width, int height, int fps) {
m_handle =
CreateCvSource(name, VideoMode{format, width, height, fps}, &m_status);
m_handle = CreateRawSource(name, true, VideoMode{format, width, height, fps},
&m_status);
}
inline bool CvSource::VerifyFormat(cv::Mat& image,
VideoMode::PixelFormat pixelFormat) {
int channels = image.channels();
switch (pixelFormat) {
case VideoMode::PixelFormat::kBGR:
if (channels == 3) {
return true;
}
break;
case VideoMode::PixelFormat::kBGRA:
if (channels == 4) {
return true;
}
break;
case VideoMode::PixelFormat::kGray:
if (channels == 1) {
return true;
}
break;
case VideoMode::PixelFormat::kRGB565:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kUYVY:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kY16:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kYUYV:
if (channels == 2) {
return true;
}
break;
case VideoMode::PixelFormat::kMJPEG:
if (channels == 1) {
return true;
}
break;
default:
break;
}
return false;
}
inline void CvSource::PutFrame(cv::Mat& image) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U) {
finalImage = image;
} else {
image.convertTo(finalImage, CV_8U);
}
int channels = finalImage.channels();
VideoMode::PixelFormat format;
if (channels == 1) {
// 1 channel is assumed Graysacle
format = VideoMode::PixelFormat::kGray;
} else if (channels == 2) {
// 2 channels is assumed YUYV
format = VideoMode::PixelFormat::kYUYV;
} else if (channels == 3) {
// 3 channels is assumed BGR
format = VideoMode::PixelFormat::kBGR;
} else if (channels == 4) {
// 4 channels is assumed BGRA
format = VideoMode::PixelFormat::kBGRA;
} else {
// TODO Error
return;
}
PutFrame(finalImage, format, true);
}
inline void CvSource::PutFrame(cv::Mat& image,
VideoMode::PixelFormat pixelFormat,
bool skipVerification) {
// We only support 8-bit images; convert if necessary.
cv::Mat finalImage;
if (image.depth() == CV_8U) {
finalImage = image;
} else {
image.convertTo(finalImage, CV_8U);
}
if (!skipVerification) {
if (!VerifyFormat(finalImage, pixelFormat)) {
// TODO Error
return;
}
}
WPI_RawFrame frame; // use WPI_Frame because we don't want the destructor
frame.data = finalImage.data;
frame.freeFunc = nullptr;
frame.freeCbData = nullptr;
frame.size = finalImage.total() * finalImage.channels();
frame.width = finalImage.cols;
frame.height = finalImage.rows;
frame.stride = finalImage.step;
frame.pixelFormat = pixelFormat;
m_status = 0;
PutSourceFrame(m_handle, image, &m_status);
PutSourceFrame(m_handle, frame, &m_status);
}
inline CvSink::CvSink(std::string_view name,
VideoMode::PixelFormat pixelFormat) {
m_handle = CreateCvSink(name, pixelFormat, &m_status);
m_handle = CreateRawSink(name, true, &m_status);
this->pixelFormat = pixelFormat;
}
inline CvSink::CvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame,
VideoMode::PixelFormat pixelFormat) {
m_handle = CreateCvSinkCallback(name, pixelFormat, processFrame, &m_status);
inline CvSink::CvSink(const CvSink& sink)
: ImageSink{sink}, pixelFormat{sink.pixelFormat} {}
inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) {
cv::Mat tmpnam;
auto retVal = GrabFrameDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t CvSink::GrabFrame(cv::Mat& image, double timeout) const {
m_status = 0;
return GrabSinkFrameTimeout(m_handle, image, timeout, &m_status);
inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t CvSink::GrabFrameNoTimeout(cv::Mat& image) const {
m_status = 0;
return GrabSinkFrame(m_handle, image, &m_status);
inline constexpr int CvSink::GetCvFormat(WPI_PixelFormat pixelFormat) {
int type = 0;
switch (pixelFormat) {
case WPI_PIXFMT_YUYV:
case WPI_PIXFMT_RGB565:
case WPI_PIXFMT_Y16:
case WPI_PIXFMT_UYVY:
type = CV_8UC2;
break;
case WPI_PIXFMT_BGR:
type = CV_8UC3;
break;
case WPI_PIXFMT_BGRA:
type = CV_8UC4;
break;
case WPI_PIXFMT_GRAY:
case WPI_PIXFMT_MJPEG:
default:
type = CV_8UC1;
break;
}
return type;
}
inline uint64_t CvSink::GrabFrameDirect(cv::Mat& image, double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrameTimeout(m_handle, rawFrame, timeout, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
inline uint64_t CvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.stride = 0;
rawFrame.pixelFormat = pixelFormat;
auto timestamp = GrabSinkFrame(m_handle, rawFrame, &m_status);
if (m_status != CS_OK) {
return 0;
}
image =
cv::Mat{rawFrame.height, rawFrame.width,
GetCvFormat(static_cast<WPI_PixelFormat>(rawFrame.pixelFormat)),
rawFrame.data, static_cast<size_t>(rawFrame.stride)};
return timestamp;
}
} // namespace cs
#endif
#endif // CSCORE_CSCORE_CV_H_

View File

@@ -221,7 +221,9 @@ class VideoSource {
/// HTTP video source.
kHttp = CS_SOURCE_HTTP,
/// CV video source.
kCv = CS_SOURCE_CV
kCv = CS_SOURCE_CV,
/// Raw video source.
kRaw = CS_SOURCE_RAW,
};
/** Connection strategy. Used for SetConnectionStrategy(). */
@@ -690,8 +692,10 @@ class HttpCamera : public VideoCamera {
/**
* A source that represents an Axis IP camera.
*
* @deprecated Use HttpCamera instead.
*/
class AxisCamera : public HttpCamera {
class [[deprecated("Use HttpCamera instead.")]] AxisCamera : public HttpCamera {
static std::string HostToUrl(std::string_view host);
static std::vector<std::string> HostToUrl(std::span<const std::string> hosts);
template <typename T>
@@ -856,7 +860,9 @@ class VideoSink {
/// MJPEG video sink.
kMjpeg = CS_SINK_MJPEG,
/// CV video sink.
kCv = CS_SINK_CV
kCv = CS_SINK_CV,
/// Raw video sink.
kRaw = CS_SINK_RAW,
};
VideoSink() noexcept = default;

View File

@@ -11,6 +11,8 @@
#include <utility>
#include <vector>
#include <wpi/deprecated.h>
#include "cscore_oo.h"
namespace cs {
@@ -350,7 +352,7 @@ inline std::vector<std::string> HttpCamera::GetUrls() const {
m_status = 0;
return ::cs::GetHttpCameraUrls(m_handle, &m_status);
}
WPI_IGNORE_DEPRECATED
inline std::vector<std::string> AxisCamera::HostToUrl(
std::span<const std::string> hosts) {
std::vector<std::string> rv;
@@ -389,6 +391,7 @@ template <typename T>
inline AxisCamera::AxisCamera(std::string_view name,
std::initializer_list<T> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
WPI_UNIGNORE_DEPRECATED
inline void ImageSource::NotifyError(std::string_view msg) {
m_status = 0;

View File

@@ -28,18 +28,18 @@ uint64_t CS_GrabRawSinkFrame(CS_Sink sink, struct WPI_RawFrame* rawImage,
uint64_t CS_GrabRawSinkFrameTimeout(CS_Sink sink, struct WPI_RawFrame* rawImage,
double timeout, CS_Status* status);
CS_Sink CS_CreateRawSink(const char* name, CS_Status* status);
CS_Sink CS_CreateRawSink(const struct WPI_String* name, CS_Bool isCv,
CS_Status* status);
CS_Sink CS_CreateRawSinkCallback(const char* name, void* data,
void (*processFrame)(void* data,
uint64_t time),
CS_Status* status);
CS_Sink CS_CreateRawSinkCallback(
const struct WPI_String* name, CS_Bool isCv, void* data,
void (*processFrame)(void* data, uint64_t time), CS_Status* status);
void CS_PutRawSourceFrame(CS_Source source, const struct WPI_RawFrame* image,
CS_Status* status);
CS_Source CS_CreateRawSource(const char* name, const CS_VideoMode* mode,
CS_Status* status);
CS_Source CS_CreateRawSource(const struct WPI_String* name, CS_Bool isCv,
const CS_VideoMode* mode, CS_Status* status);
/** @} */
#ifdef __cplusplus
@@ -54,11 +54,11 @@ namespace cs {
* @{
*/
CS_Source CreateRawSource(std::string_view name, const VideoMode& mode,
CS_Status* status);
CS_Source CreateRawSource(std::string_view name, bool isCv,
const VideoMode& mode, CS_Status* status);
CS_Sink CreateRawSink(std::string_view name, CS_Status* status);
CS_Sink CreateRawSinkCallback(std::string_view name,
CS_Sink CreateRawSink(std::string_view name, bool isCv, CS_Status* status);
CS_Sink CreateRawSinkCallback(std::string_view name, bool isCv,
std::function<void(uint64_t time)> processFrame,
CS_Status* status);
@@ -166,14 +166,14 @@ class RawSink : public ImageSink {
};
inline RawSource::RawSource(std::string_view name, const VideoMode& mode) {
m_handle = CreateRawSource(name, mode, &m_status);
m_handle = CreateRawSource(name, false, mode, &m_status);
}
inline RawSource::RawSource(std::string_view name,
VideoMode::PixelFormat format, int width,
int height, int fps) {
m_handle =
CreateRawSource(name, VideoMode{format, width, height, fps}, &m_status);
m_handle = CreateRawSource(name, false, VideoMode{format, width, height, fps},
&m_status);
}
inline void RawSource::PutFrame(wpi::RawFrame& image) {
@@ -182,12 +182,12 @@ inline void RawSource::PutFrame(wpi::RawFrame& image) {
}
inline RawSink::RawSink(std::string_view name) {
m_handle = CreateRawSink(name, &m_status);
m_handle = CreateRawSink(name, false, &m_status);
}
inline RawSink::RawSink(std::string_view name,
std::function<void(uint64_t time)> processFrame) {
m_handle = CreateRawSinkCallback(name, processFrame, &m_status);
m_handle = CreateRawSinkCallback(name, false, processFrame, &m_status);
}
inline uint64_t RawSink::GrabFrame(wpi::RawFrame& image, double timeout) const {

View File

@@ -1,228 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef CSCORE_CSCORE_RAW_CV_H_
#define CSCORE_CSCORE_RAW_CV_H_
#ifdef CSCORE_CSCORE_CV_H_
#error "Cannot include both cscore_cv.h and cscore_raw_cv.h in the same file"
#endif
#include <functional>
#include <opencv2/core/mat.hpp>
#include "cscore_raw.h"
namespace cs {
/**
* A source for using the raw frame API to provide opencv images.
*
* If you are using the WPILib OpenCV builds, do not use this, and
* instead include "cscore_cv.h" to get a more performant version.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of OpenCV that are not 3. If using OpenCV 3, use
* CvSource.
*/
class RawCvSource : public RawSource {
public:
RawCvSource() = default;
/**
* Create a Raw OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param mode Video mode being generated
*/
RawCvSource(std::string_view name, const VideoMode& mode);
/**
* Create a Raw OpenCV source.
*
* @param name Source name (arbitrary unique identifier)
* @param pixelFormat Pixel format
* @param width width
* @param height height
* @param fps fps
*/
RawCvSource(std::string_view name, VideoMode::PixelFormat pixelFormat,
int width, int height, int fps);
/**
* Put an OpenCV image and notify sinks.
*
* <p>Only 8-bit single-channel or 3-channel (with BGR channel order) images
* are supported. If the format, depth or channel order is different, use
* cv::Mat::convertTo() and/or cv::cvtColor() to convert it first.
*
* @param image OpenCV image
*/
void PutFrame(cv::Mat& image);
private:
wpi::RawFrame rawFrame;
};
/**
* A sink for user code to accept raw video frames as OpenCV images.
*
* If you are using the WPILib OpenCV builds, do not use this, and
* instead include "cscore_cv.h" to get a more performant version.
*
* This is not dependent on any opencv binary ABI, and can be used
* with versions of OpenCV that are not 3. If using OpenCV 3, use
* CvSink.
*/
class RawCvSink : public RawSink {
public:
RawCvSink() = default;
/**
* Create a sink for accepting OpenCV images.
*
* <p>WaitForFrame() must be called on the created sink to get each new
* image.
*
* @param name Source name (arbitrary unique identifier)
*/
explicit RawCvSink(std::string_view name);
/**
* Create a sink for accepting OpenCV images in a separate thread.
*
* <p>A thread will be created that calls WaitForFrame() and calls the
* processFrame() callback each time a new frame arrives.
*
* @param name Source name (arbitrary unique identifier)
* @param processFrame Frame processing function; will be called with a
* time=0 if an error occurred. processFrame should call GetImage()
* or GetError() as needed, but should not call (except in very
* unusual circumstances) WaitForImage().
*/
RawCvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrame(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeout(cv::Mat& image);
/**
* Wait for the next frame and get the image.
* Times out (returning 0) after timeout seconds.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameDirect(cv::Mat& image, double timeout = 0.225);
/**
* Wait for the next frame and get the image. May block forever.
* The provided image will have three 8-bit channels stored in BGR order.
*
* @return Frame time, or 0 on error (call GetError() to obtain the error
* message); the frame time is in the same time base as wpi::Now(),
* and is in 1 us increments.
*/
[[nodiscard]]
uint64_t GrabFrameNoTimeoutDirect(cv::Mat& image);
private:
wpi::RawFrame rawFrame;
};
inline RawCvSource::RawCvSource(std::string_view name, const VideoMode& mode)
: RawSource{name, mode} {}
inline RawCvSource::RawCvSource(std::string_view name,
VideoMode::PixelFormat format, int width,
int height, int fps)
: RawSource{name, format, width, height, fps} {}
inline void RawCvSource::PutFrame(cv::Mat& image) {
m_status = 0;
rawFrame.data = reinterpret_cast<char*>(image.data);
rawFrame.width = image.cols;
rawFrame.height = image.rows;
rawFrame.totalData = image.total() * image.channels();
rawFrame.pixelFormat =
image.channels() == 3 ? WPI_PIXFMT_BGR : WPI_PIXFMT_GRAY;
PutSourceFrame(m_handle, rawFrame, &m_status);
}
inline RawCvSink::RawCvSink(std::string_view name) : RawSink{name} {}
inline RawCvSink::RawCvSink(std::string_view name,
std::function<void(uint64_t time)> processFrame)
: RawSink{name, processFrame} {}
inline uint64_t RawCvSink::GrabFrame(cv::Mat& image, double timeout) {
cv::Mat tmpnam;
auto retVal = GrabFrameDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t RawCvSink::GrabFrameNoTimeout(cv::Mat& image) {
cv::Mat tmpnam;
auto retVal = GrabFrameNoTimeoutDirect(tmpnam);
if (retVal <= 0) {
return retVal;
}
tmpnam.copyTo(image);
return retVal;
}
inline uint64_t RawCvSink::GrabFrameDirect(cv::Mat& image, double timeout) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = WPI_PixelFormat::WPI_PIXFMT_BGR;
m_status = RawSink::GrabFrame(rawFrame, timeout);
if (m_status <= 0) {
return m_status;
}
image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
return m_status;
}
inline uint64_t RawCvSink::GrabFrameNoTimeoutDirect(cv::Mat& image) {
rawFrame.height = 0;
rawFrame.width = 0;
rawFrame.pixelFormat = WPI_PixelFormat::WPI_PIXFMT_BGR;
m_status = RawSink::GrabFrameNoTimeout(rawFrame);
if (m_status <= 0) {
return m_status;
}
image = cv::Mat{rawFrame.height, rawFrame.width, CV_8UC3, rawFrame.data};
return m_status;
}
} // namespace cs
#endif // CSCORE_CSCORE_RAW_CV_H_

View File

@@ -80,6 +80,8 @@ static VideoMode::PixelFormat ToPixelFormat(__u32 pixelFormat) {
return VideoMode::kRGB565;
case V4L2_PIX_FMT_BGR24:
return VideoMode::kBGR;
case V4L2_PIX_FMT_ABGR32:
return VideoMode::kBGRA;
case V4L2_PIX_FMT_GREY:
return VideoMode::kGray;
case V4L2_PIX_FMT_Y16:
@@ -102,6 +104,8 @@ static __u32 FromPixelFormat(VideoMode::PixelFormat pixelFormat) {
return V4L2_PIX_FMT_RGB565;
case VideoMode::kBGR:
return V4L2_PIX_FMT_BGR24;
case VideoMode::kBGRA:
return V4L2_PIX_FMT_ABGR32;
case VideoMode::kGray:
return V4L2_PIX_FMT_GREY;
case VideoMode::kY16:

View File

@@ -7,9 +7,6 @@
#include <wpi/timestamp.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
@implementation UsbCameraDelegate
- (id)init {
@@ -24,6 +21,8 @@
(void)sampleBuffer;
(void)connection;
auto currentTime = wpi::Now();
auto sharedThis = self.cppImpl.lock();
if (!sharedThis) {
return;
@@ -52,16 +51,12 @@
return;
}
size_t currSize = width * 3 * height;
auto tmpMat = cv::Mat(height, width, CV_8UC4, baseaddress, rowBytes);
auto image = sharedThis->AllocImage(cs::VideoMode::PixelFormat::kBGR, width,
height, currSize);
cv::cvtColor(tmpMat, image->AsMat(), cv::COLOR_BGRA2BGR);
std::unique_ptr<cs::Image> image = cs::CreateImageFromBGRA(
sharedThis.get(), width, height, rowBytes, reinterpret_cast<uint8_t*>(baseaddress));
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
sharedThis->objcPutFrame(std::move(image), wpi::Now());
sharedThis->objcPutFrame(std::move(image), currentTime);
}
@end

View File

@@ -18,7 +18,6 @@
#include "Instance.h"
#include "c_util.h"
#include "cscore_cpp.h"
#include "opencv2/imgproc.hpp"
#include "UsbCameraImpl.h"
namespace cs {
@@ -135,7 +134,11 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
std::vector<UsbCameraInfo> retval;
NSArray<AVCaptureDeviceType>* deviceTypes = @[
AVCaptureDeviceTypeBuiltInWideAngleCamera,
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 140000
AVCaptureDeviceTypeExternal
#else
AVCaptureDeviceTypeExternalUnknown
#endif
];
AVCaptureDeviceDiscoverySession* session = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:deviceTypes

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