Compare commits

...

119 Commits

Author SHA1 Message Date
Peter Johnson
60c2f59051 C++ CameraServer: initialize default Usb camera device number (#1601)
Otherwise just plain StartAutomaticCapture() starts with random number.
2019-02-14 22:39:40 -08:00
Peter Johnson
d55ca191b8 CameraServer: Add switched camera support (#1600)
This adds a new function "addSwitchedCamera" that creates and publishes a
virtual camera where the published stream information is consistent even if
the mjpeg server source is switched to a different camera.

Previously, changing the source of the mjpeg server resulted in updating the
stream information published for that source.
2019-02-14 22:05:40 -08:00
Sam Carlberg
e8b24717c7 C++ Shuffleboard fixes (#1595)
* Fix C++ ShuffleboardComponent template type

* Fix `WithWidget(WidgetType&)`not being properly capitalized

* Fix data members across dll boundaries by using enum for built in types
2019-02-14 20:44:30 -08:00
Tyler Veness
182758c05b Fix Ultrasonic sensor runner thread (#1598)
When there is more than one ultrasonic sensor, only the last sensor
instantiated would work due to incorrect array index management. This
replaces the previous approach with range-based for loops like the C++
implementation.

Supersedes #1589.
2019-02-14 20:25:56 -08:00
Peter Johnson
74f7ba04b0 Java PIDBase: Make setPIDSourceType and getPIDSourceType public (#1599)
Fixes #1596.
2019-02-13 23:41:05 -08:00
Thad House
997d4fdf47 Update HAL GetStackTrace to properly report user errors (#1594)
With the move of the HAL, the old value no longer worked, as the JNI call is in a different namespace
2019-02-11 20:17:31 -08:00
Thad House
76d9e26633 uv: Add reuse to pipe (#1577)
Needed for a reconnection API
2019-02-09 00:09:22 -08:00
Thad House
a230c814cb Add support for WPILib vscode extension for allwpilib C++ intellisense (#1590) 2019-02-09 00:07:48 -08:00
Thad House
12cb77cd7c Fix DS Comm button tests (#1591)
Mac requires the double braces
2019-02-09 00:07:24 -08:00
Thad House
8a9822a96b Allow multiple instances of the same PDP (#1582)
Previously multiple instances would overrun with each other. This make all instances get the same HAL handle.
2019-02-06 22:51:34 -08:00
Thad House
a9371a7586 Fix missing exposure property on windows USB cameras (#1571) 2019-02-06 22:49:55 -08:00
Peter Johnson
6992f5421f cscore: Avoid crash if invalid (null) source set in MjpegServer (#1585)
Fixes robotpy/robotpy-cscore#60
2019-02-03 00:22:07 -08:00
Tyler Veness
43696956d2 Fix Watchdog incorrectly resetting expiration flag upon disable (#1580)
Resetting the flag should only occur in Enable() and Reset().
IterativeRobotBase needs the flag to remain set to print epochs after
disabling the Watchdog.
2019-02-02 00:22:07 -08:00
Thad House
ae3fd5adac Fix docs search having unspecified module directory (#1568)
Also add Doxygen comment for C++ filesystem API.
2019-02-02 00:20:57 -08:00
Jaci Brunning
404666b298 Fix Halsim DS Button Format (#1583) 2019-02-01 23:23:04 -08:00
sciencewhiz
1eb4c99d15 Update README for 2019 changes (#1569)
Fixes #1562
2019-02-01 23:05:41 -08:00
Thad House
910b9f3af7 Add support for camera descriptions on windows (#1572)
For now, has to force enumerate all cameras to find the matching one
2019-02-01 23:04:12 -08:00
Thad House
09d90b02fb Remove prints and unecessary items from windows GetNetworkInterfaces (#1573) 2019-02-01 23:02:12 -08:00
Peter Johnson
0e1f9c2ed2 ntcore: Read ini escaped quotes correctly (#1579) 2019-02-01 23:01:00 -08:00
Thad House
f156a00117 wpiutil uv: Pass IPC value of listening pipe to accepted pipe (#1576)
Otherwise, the acception fails.
2019-01-30 20:37:28 -08:00
Thad House
4a6087ed56 Disable watchdog test on mac (#1578)
It can't be held property on the asure test system
2019-01-30 20:35:16 -08:00
Peter Johnson
88a09dd13a cscore: Handle USB camera integer menus (#1561)
The Pi Camera is one of these.  Previously, integers were just being
cast to a string instead of formatted as a string.
2019-01-24 22:46:29 -08:00
Christopher Cantrell
7d19596367 Changed terminology from "Overload" to "Override" (#1563) 2019-01-24 22:45:05 -08:00
Jaci Brunning
bd05dfa1c7 Fix ds_socket matchinfo (#1555) 2019-01-16 21:32:34 -08:00
Peter Johnson
05d6660a6b cscore: Add config json to VideoSink (#1543)
Same format as VideoSource.

Refactor properties json handling into PropertyContainer.
2019-01-11 20:33:05 -08:00
Peter Johnson
1349dd4bd8 Make MjpegServer functions public (#1545) 2019-01-11 17:53:44 -08:00
Peter Johnson
fdf298b172 CameraServer: Return MjpegServer from StartAutomaticCapture(VideoSource) (#1544) 2019-01-11 16:44:49 -08:00
Peter Johnson
453a9047e4 Fix cmake hal sim build (#1539)
A source directory was named incorrectly in CamelCase.
2019-01-11 02:38:35 -08:00
Peter Johnson
e97e7a7611 Add DriverStationSim.notifyNewData (#1537) 2019-01-09 23:47:42 -08:00
Thad House
308bdbe298 cscore: Windows UsbCamera: Use custom windows message for initial connection (#1534)
WM_CREATE is being called at a time where the message pump cannot be called back into.
2019-01-09 22:52:24 -08:00
Peter Johnson
f889b45d59 cscore: MjpegServer: Fix FPS limiting (#1536)
This uses a 1-second window average to try to hit the desired FPS.
2019-01-09 22:50:34 -08:00
Peter Johnson
444b899a9f Java: Fix Timer.get() handling of accumulated time (#1531)
Fixes #1530.
2019-01-08 19:43:49 -08:00
Tyler Veness
f121ccff0d Avoid Watchdog thread clobbering m_isExpired flag after callback (#1527) 2019-01-08 19:37:59 -08:00
Peter Johnson
bc2c932f92 Fix MotorSafety deadlock (#1526)
Some instances of StopMotor (most notably DifferentialDrive) call Feed(),
which deadlocks due to Check() holding the same lock.

Fixes #1525.
2019-01-02 20:58:39 -08:00
Thad House
6bdd7ce506 Update docs for disabled init to match all the other modes (#1523)
Closes #1522
2018-12-31 19:11:35 -08:00
Thad House
c12d7729e3 Update examples to use VictorSPX's rather then Sparks (#1521) 2018-12-31 13:45:09 -08:00
Dustin Spicuzza
3635116049 cscore: set charset of displayed pages (#1520)
All of our strings are UTF-8, but the default charset for HTML<5 was ISO-8859-1
2018-12-30 23:49:24 -08:00
Peter Johnson
6105873cbe Provide alternate device paths info for USB cameras (#1519)
This is primarily for use on Linux to get by-id or by-path device names.

This information is now part of UsbCameraInfo.

A new entry point was added to UsbCamera to get that camera's UsbCameraInfo.

The alternate paths are also returned in EnumerateUsbCameras.
2018-12-30 11:48:18 -08:00
Sam Carlberg
80f87ff8ad Allow video sources to be added to Shuffleboard (#1453)
Add a Sendable wrapper for VideoSource objects.

Add convenience methods for adding video sources directly to containers
so users won't have to manually wrap video sources.
2018-12-30 11:45:41 -08:00
Peter Johnson
a2368a6199 Watchdog: add timeout message suppression feature
Was part of reverted #1486
2018-12-30 00:16:50 -08:00
Peter Johnson
ae3cb6c83b Watchdog.java: add comment fixes from reverted #1442 2018-12-30 00:16:50 -08:00
Peter Johnson
f0f196e5b3 Revert "MotorSafety: Use Watchdog instead of DS class polling (#1442)"
This reverts commit 26e8e587f9.
2018-12-30 00:16:50 -08:00
Peter Johnson
7c35355d29 Revert "Suppress Watchdog's generic timeout message in MotorSafety (#1486)"
This reverts commit 41596608cc.
2018-12-30 00:16:50 -08:00
Peter Johnson
75cc09a9e4 Revert "Suppress timeout message in C++ MotorSafety class (#1512)"
This reverts commit 300eeb330d.
2018-12-30 00:16:50 -08:00
Peter Johnson
0e2e180635 PowerDistributionPanelTest.cpp: Include thread 2018-12-30 00:16:50 -08:00
Sam Carlberg
01d1322066 Add constants for built-in Shuffleboard widgets and layouts (#1468)
Prevents users from having to remember (and correctly type) the names of Shuffleboard widgets.
2018-12-29 17:22:47 -08:00
Peter Johnson
ceed1d74dc ntcore: Strip whitespace from IP addresses (#1516) 2018-12-29 14:47:25 -08:00
Peter Johnson
e1bf623997 cscore: handle HTTP stream hangs (#1513)
Automatically disconnect the HTTP stream if no frames have been received
for 1 second.
2018-12-29 14:08:45 -08:00
Thad House
d46ce13ffe Fix CAN API timing out incorrectly (#1497)
HAL_GetFPGATime returns 0 if it starts with a non zero status.

Always use monotonic clock for CAN times, rather then trying to sync FPGA.

Change timeout from 50 ms to 100 ms.
2018-12-29 13:57:23 -08:00
Tyler Veness
300eeb330d Suppress timeout message in C++ MotorSafety class (#1512)
wpilibj's default constructor is already correct.
2018-12-27 14:01:02 -06:00
Andrew Dassonville
d817001259 Only write version information on real robot (#1510)
Writing to the versions file throws an exception on Windows, and might
write weird files on Linux.
2018-12-27 00:59:49 -06:00
Austin Shalit
8ac4b113a5 Fix build on macOS 10.14.2 (#1509) 2018-12-25 22:58:04 -06:00
Tyler Veness
f3864e9abb Move deprecated message in C++ from class level to constructor (#1505)
This prevents deprecation messages when including a header but not
instantiating the class. SampleRobot was addressed by #1503.
2018-12-25 01:14:24 -06:00
Thad House
799c3ea8a6 Disable gyro sim test on mac (#1506)
It fails with a mutex error very often, and we have not been able to figure out the issue.

It breaks CI.
2018-12-25 01:13:59 -06:00
David Vo
8d95c38e39 Watchdog: Fix potential IllegalFormatException (#1508)
If an epoch name contains a % symbol, this could've lead to an exception
at runtime where the number of arguments mismatches the format string.
2018-12-25 01:12:44 -06:00
Thad House
a7f4e29b73 Update versioning and vscode plugin (#1498)
versioning fixes multi tag issue
2018-12-24 15:46:41 -06:00
Thad House
b88369f5e8 Move deprecation warning for SampleRobot to constructor (#1503)
This way, the warning doesn't occur when including wpilib.h

Closes #1501
2018-12-24 15:46:12 -06:00
Thad House
ce6f1d0588 Change deperecation year for serial port direct constructor (#1504) 2018-12-24 15:17:19 -06:00
Thad House
f163216a4c Add support for raspbian extraction (#1494)
This is how I did it in C#, and it was the most reliable way I could find.
2018-12-24 15:15:15 -06:00
Thad House
c449ef1064 Unconditionally await in awaitUntil (#1499)
Negative numbers are properly handled, which will reduce chances of deadlocks.
2018-12-24 15:07:47 -06:00
Thad House
6593f4346e Default to DS attached to true in sim (#1500)
Otherwise, it is required to be set manually, which isn't obvious.

This is because the HighLevel DS classes check that the ds is attached before enabling
2018-12-24 02:06:53 -06:00
Thad House
ce1367a115 Make JNI library not found message clearer (#1495)
Closes #1472
2018-12-21 00:26:53 -08:00
Thad House
0d7d880261 Renable full Java 8 Compatibility (#1493) 2018-12-21 00:25:23 -08:00
Thad House
ca2acec88c Update to NI Libraries 2019v12 (#1492) 2018-12-21 00:24:44 -08:00
Thad House
3721463eb3 Don't use symbolic path searching for USB detection (#1491)
The USB directories we care about are not symbolic links, so we can ignore them.

There was a recursive symbolic link, which was causing failure.
2018-12-19 14:40:01 -08:00
Thad House
6e8f8be370 Make C++ examples able to run GradleRIO unit tests (#1490)
Closes #1484
2018-12-17 09:26:20 -08:00
Thad House
d84240d8e9 Bump opencv version to 3.4.4-4 (#1489) 2018-12-14 20:17:51 -08:00
Thad House
1823cb2b68 Fix periodic can packets not being received properly (#1488)
Constant calls would result in timeout, because a valid packet wasn't being returned early.

Also fix data copies (was copying in wrong direction).
2018-12-14 17:43:48 -08:00
Tyler Veness
41596608cc Suppress Watchdog's generic timeout message in MotorSafety (#1486) 2018-12-14 10:53:33 -08:00
Thad House
0c3b488e18 Make ntcore instances not smart pointers (#1485)
During static shutdown, the instances hang on windows 7, so let them leak instead.
2018-12-13 21:33:58 -08:00
Thad House
7d7af287f6 Add empty classes to command/classes creation (#1479)
Closes #1478
2018-12-09 21:25:37 -08:00
Thad House
4119622994 Fix characters in license file (#1480)
Causes wpiformat errors on windows.
2018-12-09 21:23:19 -08:00
Peter Johnson
d528a77963 wpiutil: Fix resources build (#1476) 2018-12-09 11:45:11 -08:00
Peter Johnson
dab7e1b7a2 Add multiCameraServer to cmake build (#1477) 2018-12-09 11:44:45 -08:00
Peter Johnson
ab49345460 Add missing param javadoc comment and fix argument order inconsistency (#1475) 2018-12-09 08:37:09 -08:00
Peter Johnson
608d82423d wpiutil: Use scoped connections to ensure cleanup (#1474) 2018-12-09 01:36:36 -08:00
Peter Johnson
e0e15eafeb wpiutil: Add shared resources for http servers
Files are named only with major.minor versions.  Full versions are:
* bootstrap 4.1.3
* coreui 2.1.8
* feather 4.8.0
* jquery 3.3.1
* popper 1.14.4
2018-12-09 00:58:04 -08:00
Peter Johnson
0fb24538a7 wpiutil: HttpServerConnection: add SendStaticResponse
This function is intended for use when the content is a static const variable.
It allows gzipped static data, but doesn't provide the functionality to ungzip
it if the client doesn't support gzip.  This is because it would add a
dependency on zlib and basically all clients support gzip.

Change extraHeader on all Send functions to include the final newline,
this makes it easier to build up extra headers incrementally.

Expose sig::Connection for messageComplete and headerConn to allow them to
be disconnected by users of the class.  This is commonly needed for things
like WebSocket upgrades.
2018-12-09 00:58:04 -08:00
Thad House
d65547ea74 Bump OpenCV version (#1471) 2018-12-08 23:12:39 -08:00
Peter Johnson
bfe15245a6 Add multiCameraServer executable (#1422)
This standalone CameraServer reads its configuration from a json file.
2018-12-07 23:57:37 -08:00
Nicolas Machado
ff58c5156a Change SmartDashboard type of PIDBase.cpp to match PIDBase.java (#1470) 2018-12-07 19:40:31 -08:00
Tyler Veness
6d4326a560 Rate-limit Watchdog timeout prints to 1Hz (#1459) 2018-12-07 19:39:02 -08:00
Sam Carlberg
97ba195b88 Fix LiveWindow attempting to start listeners on uninitialized sendables (#1463)
Additionally adds a defensive check in SendableBuilderImpl to avoid the NPE.
2018-12-07 19:38:22 -08:00
Thad House
3d546428ab Add missing SubTableListener methods to C++ NetworkTable (#1465) 2018-12-06 23:27:49 -08:00
Dustin Spicuzza
b64dfacff3 DriverStation: fix error checking for GetXXXAxis and GetXXXPov (#1469)
Fixes #1436
2018-12-06 22:31:14 -08:00
Peter Johnson
dcbf02a1ec Update auto SPI for timestamp changes (#1457)
The 2019 FPGA image switched the output of auto SPI from plain bytes to a
sequence of 32-bit words (timestamp, then words with the byte values in the
least significant byte of each word).

In addition to changing the HAL and simulators to reflect this, add piecewise
integration support to wpilibc/wpilibj SPI to take advantage of the timestamps
and use it in the ADXRS450 gyro.
2018-12-06 22:29:20 -08:00
Thad House
7e1ec28839 NI image 2019v9 (#1467) 2018-12-06 21:59:13 -08:00
Thad House
ef16317f8f Update NativeUtils and Doxygen (#1462)
Adds debug stripping to executables.
Copies .debug and .pdb files to install directories to ease debugging.

Update to v0.5 of doxygen, which fixes the download location.
2018-12-05 00:27:18 -08:00
Tyler Veness
26e8e587f9 MotorSafety: Use Watchdog instead of DS class polling (#1442) 2018-12-01 01:34:52 -08:00
Dustin Spicuzza
0d0492bfcc HAL: Add additional error messages to HAL_GetErrorMessage (#965) 2018-12-01 00:06:30 -08:00
Tyler Veness
3b33abfc7b Make Watchdog use single thread dispatch (#1347)
Notifier has one thread per instance because the callbacks must be
asynchronous. Watchdog callbacks can be synchronous, so this overhead
can be done away with via a scheduler thread akin to what the HAL
Notifier does.
2018-12-01 00:05:33 -08:00
Peter Johnson
99033481e0 Add netconsoleTee
This is similar to netconsoleServer, but is designed to operate as a tee
in a pipeline (it reads console data from stdin).
2018-11-30 23:06:31 -08:00
Peter Johnson
b4901985b7 Add netconsoleServer
This is a updated version of netconsole-host that can serve either as a
new-style TCP riolog or output UDP to either localhost or broadcast.
2018-11-30 23:06:31 -08:00
Peter Johnson
97edb6c68f wpiutil: uv::Buffer: Add Dup() function 2018-11-30 23:06:31 -08:00
Peter Johnson
73de3364b7 AxisCamera constructor: add StringRef overload (#1458)
This avoids a conversion ambiguity when StringRef is passed.
2018-11-30 22:35:56 -08:00
Thad House
5551981b3f Upgrade to Gradle 5.0 (#1444)
Also skip raspbian for gazebo.
2018-11-29 23:17:06 -08:00
Jaci Brunning
90572a3cc5 Add wpilibc and wpilibj skeleton templates (#1441)
See also vscode-wpilib#132.
2018-11-29 22:15:17 -08:00
Thad House
c405188052 Fix directory iterators not working correctly (#1454)
The size of the directory_entry was different between translation units.
This was caused by the FILE_OFFSET_BITS macro when building wpiutil.
Removing that fixes the issue.
Should fix NavX USB issues.
2018-11-29 00:24:10 -08:00
Thad House
bea0565ac9 Update to OpenCV 3.4.4 (#1452) 2018-11-28 12:26:56 -08:00
Peter Johnson
0b03454366 wpiutil: Replace LLVM Optional with C++17-compatible optional
Imported from https://github.com/akrzemi1/Optional with minor changes:
- Compiler conditional simplifications (we only use recent versions)
- Move from std::experimental to wpi namespace
- Change tests to integrate with Google Test

Update LLVM use cases.
2018-11-28 12:23:56 -08:00
Peter Johnson
489701cacc wpiutil: Rename Optional.h to LLVMOptional.h 2018-11-28 12:23:56 -08:00
Tyler
a769d56ec1 Create C++ ShuffleBoard example (#1438) 2018-11-28 12:13:38 -08:00
Sam Carlberg
6f0c185a05 Add methods to change the selected tab in the Shuffleboard app (#1448) 2018-11-27 22:12:50 -08:00
Sam Carlberg
a60f312d19 Add eager null checks in drive base classes (#1447)
All null motors will be specified in the error messages.
2018-11-27 18:11:56 -08:00
Tyler Veness
acb786a791 Remove MotorSafetyHelper, create MotorSafety base class instead (#562)
Most of the MotorSafety implementation was moved into the MotorSafety base
class. SafePWM's inheritance of MotorSafety was moved into PWM to
eliminate Java needing a helper class.

In Java, a helper class for Sendable (SendableImpl) was added due to
lack of multiple inheritance.
2018-11-22 21:15:26 -08:00
Peter Johnson
df347e3d80 wpiutil: uv::Process: Revamp args/options approach (#1434) 2018-11-19 19:54:54 -08:00
Peter Johnson
e4aa45f34b wpiutil: WebSocket: Fix Sec-Websocket-Accept computation
It needs to use the raw SHA1 bytes, not the hex-decoded text.
2018-11-19 19:24:28 -08:00
Peter Johnson
75cc3cda28 wpiutil: SHA1: Add method to get raw bytes instead of hex 2018-11-19 19:24:28 -08:00
Sam Carlberg
45f4472d42 Add mechanism to control Shuffleboard recordings and add event markers (#1414) 2018-11-18 23:15:30 -08:00
Thad House
69cb53b51b Add support for USB Webcams on Windows (#1390) 2018-11-17 23:16:35 -08:00
Tyler Veness
70a66fc943 Make Gradle show stdout for wpilibj tests (#1431) 2018-11-17 20:29:27 -08:00
Austin Schuh
9207d788ab Convert ReadInterrupt* to return int64_t time (#1417)
HAL_ReadInterruptRisingTimestamp and HAL_ReadInterruptFallingTimestamp
return time as a double.  Instead, keep the raw integer count and move the
double conversion into the C++ and Java code.  This enables comparison of the
time with other timers.
2018-11-15 21:22:03 -08:00
Thad House
ef3a31aa20 Add an example of using the HAL directly (#1374)
This is useful for advanced users, and shows a few things like error messages, and DS things that are needed.
2018-11-15 19:33:50 -08:00
Tyler Veness
63775362fe Remove SynchronousPID class (#1429)
PR #1300 supersedes it, but won't be merged until the 2020 season. Since
SynchronousPID hasn't been used during a season, it would be best to
just remove it to avoid breakage when we deprecate and remove it again.
2018-11-15 19:25:31 -08:00
Tyler Veness
55493b0c18 Fix polymorphic exception types being caught by value (#1426)
Catching these by value breaks the build with GCC 8.2.1.
2018-11-12 19:39:49 -08:00
Tyler Veness
1696557c46 Fix deprecation warning in wpilibj shuffleboard example (#1427)
TimedRobot should be used instead of IterativeRobot.
2018-11-12 19:38:50 -08:00
Thad House
ecd376be4c Export C functions from JNI statically linked library (#1425)
The JNI libraries are useful from other tools, so they should contain the C functions.
2018-11-12 19:38:35 -08:00
Peter Johnson
f54c0f70f6 Update opencv to 3.4.3-19 (#1424) 2018-11-11 19:27:20 -08:00
Peter Johnson
9bc998f4b0 cscore: Add JSON for source settings (#1423)
This allows save and restore of camera settings.  The restore is a bit
smarter than the save.

* Fix mime types in mjpeg server

* wpiutil: WPI_LOG: Make sure level is an unsigned int
2018-11-10 20:30:02 -08:00
337 changed files with 12594 additions and 2481 deletions

View File

@@ -0,0 +1,6 @@
{
"enableCppIntellisense": true,
"currentLanguage": "cpp",
"projectYear": "2019",
"teamNumber": 0
}

View File

@@ -25,10 +25,10 @@ Using Gradle makes building WPILib very straightforward. It only has a few depen
- A C++ compiler
- On Linux, GCC works fine
- On Windows, you need Visual Studio 2015 (the free community edition works fine).
- On Windows, you need Visual Studio 2017 (the free community edition works fine).
Make sure to select the C++ Programming Language for installation
- [ARM Compiler Toolchain](http://first.wpi.edu/FRC/roborio/toolchains/)
* Note that for 2017-2018 and beyond, you will need version 5 or greater of GCC
- [ARM Compiler Toolchain](https://github.com/wpilibsuite/toolchain-builder/releases)
* Note that for 2019 and beyond, you should use version 6 or greater of GCC
- Doxygen (Only required if you want to build the C++ documentation)
## Setup
@@ -79,23 +79,18 @@ There are a few tasks other than `build` available. To see them, run the meta-ta
wpiformat can be executed anywhere in the repository via `py -3 -m wpiformat` on Windows or `python3 -m wpiformat` on other platforms.
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
## Publishing
If you are building to test with the Eclipse plugins or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
If you are building to test with other dependencies or just want to export the build as a Maven-style dependency, simply run the `publish` task. This task will publish all available packages to ~/releases/maven/development. If you need to publish the project to a different repo, you can specify it with `-Prepo=repo_name`. Valid options are:
- development - The default repo.
- beta - Publishes to ~/releases/maven/beta.
- stable - Publishes to ~/releases/maven/stable.
- release - Publishes to ~/releases/maven/release.
The following maven targets a published by this task:
- edu.wpi.first.wpilib.cmake:cpp-root:1.0.0 - roboRIO C++
- edu.wpi.first.wpilibc.simulation:WPILibCSim:0.1.0 - Simulation C++
- edu.wpi.first.wpilibj:wpilibJavaFinal:0.1.0-SNAPSHOT - roboRIO Java
- edu.wpi.first.wpilibj:wpilibJavaSim:0.1.0-SNAPSHOT - Simulation Java
- edu.wpi.first.wpilibj.simulation:SimDS:0.1.0-SNAPSHOT - The driverstation for controlling simulation.
- org.gazebosim:JavaGazebo:0.1.0-SNAPSHOT - Gazebo protocol for Java.
The maven artifacts are described in [MavenArtifacts.md](MavenArtifacts.md)
## Structure and Organization

View File

@@ -31,6 +31,13 @@ sigslot wpiutil/src/main/native/include/wpi/Signal.h
wpiutil/src/test/native/cpp/sigslot/
tcpsockets wpiutil/src/main/native/cpp/TCP{Stream,Connector,Acceptor}.cpp
wpiutil/src/main/native/include/wpi/TCP*.h
Optional wpiutil/src/main/native/include/wpi/optional.h
wpiutil/src/test/native/cpp/test_optional.cpp
Bootstrap wpiutil/src/main/native/resources/bootstrap-*
CoreUI wpiutil/src/main/native/resources/coreui-*
Feather Icons wpiutil/src/main/native/resources/feather-*
jQuery wpiutil/src/main/native/resources/jquery-*
popper.js wpiutil/src/main/native/resources/popper-*
==============================================================================
@@ -212,3 +219,155 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================
Optional License
==============================================================================
Copyright (C) 2011 - 2017 Andrzej Krzemienski.
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
==============================================================================
Bootstrap License
==============================================================================
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
==============================================================================
CoreUI License
==============================================================================
Copyright (c) 2018 creativeLabs tukasz Holeczek.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
==============================================================================
Feather Icons License
==============================================================================
Copyright (c) 2013-2017 Cole Bemis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==============================================================================
jQuery License
==============================================================================
Copyright JS Foundation and other contributors, https://js.foundation/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
==============================================================================
popper.js License
==============================================================================
Copyright (c) 2016 Federico Zivolo and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -166,6 +166,9 @@ jobs:
sudo tar xvzf build/jdk.tar.gz -C /Library/Java/JavaVirtualMachines/
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home/
displayName: 'Setup JDK'
- script: |
rm /Users/vsts/.gradle/init.d/log-gradle-version-plugin.gradle
displayName: 'Delete Version init script'
- task: Gradle@2
inputs:
workingDirectory: ''

View File

@@ -1,12 +1,14 @@
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.2'
id 'edu.wpi.first.NativeUtils' version '2.0.1'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.3'
id 'edu.wpi.first.NativeUtils' version '2.1.2'
id 'edu.wpi.first.GradleJni' version '0.3.1'
id 'edu.wpi.first.GradleVsCode' version '0.6.1'
id 'edu.wpi.first.GradleVsCode' version '0.7.1'
id 'idea'
id 'com.gradle.build-scan' version '1.15.1'
id 'visual-studio'
id 'com.gradle.build-scan' version '2.0.2'
id 'net.ltgt.errorprone' version '0.6' apply false
id 'com.github.johnrengelman.shadow' version '4.0.3' apply false
}
repositories {
@@ -98,5 +100,5 @@ subprojects {
}
wrapper {
gradleVersion = '4.9'
gradleVersion = '5.0'
}

View File

@@ -62,7 +62,7 @@ class SingleNativeBuild implements Plugin<Project> {
components.each { component ->
if (component.name == "${nativeName}Base") {
base = (NativeLibrarySpec) component
} else if (component.name == "${nativeName}") {
} else if (component.name == "${nativeName}" || component.name == "${nativeName}JNI") {
subs << component
}
}

View File

@@ -51,3 +51,7 @@ endif()
install(FILES cameraserver-config.cmake DESTINATION ${cameraserver_config_dir})
install(EXPORT cameraserver DESTINATION ${cameraserver_config_dir})
file(GLOB multiCameraServer_src multiCameraServer/src/main/native/cpp/*.cpp)
add_executable(multiCameraServer ${multiCameraServer_src})
target_link_libraries(multiCameraServer cameraserver)

View File

@@ -0,0 +1,62 @@
plugins {
id 'java'
id 'application'
id 'cpp'
id 'visual-studio'
}
apply plugin: 'edu.wpi.first.NativeUtils'
apply from: "${rootDir}/shared/config.gradle"
ext {
staticCvConfigs = [multiCameraServerCpp: []]
useJava = true
useCpp = true
skipDev = true
}
apply from: "${rootDir}/shared/opencv.gradle"
mainClassName = 'Main'
apply plugin: 'com.github.johnrengelman.shadow'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.code.gson:gson:2.8.5'
compile project(':wpiutil')
compile project(':ntcore')
compile project(':cscore')
compile project(':cameraserver')
}
model {
components {
multiCameraServerCpp(NativeExecutableSpec) {
targetBuildTypes 'release'
sources {
cpp {
source {
srcDirs = ['src/main/native/cpp']
includes = ['**/*.cpp']
}
exportedHeaders {
srcDirs = ['src/main/native/include']
includes = ['**/*.h']
}
}
}
binaries.all { binary ->
lib project: ':cameraserver', library: 'cameraserver', linkage: 'static'
lib project: ':ntcore', library: 'ntcore', linkage: 'static'
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
}
}
}
}

View File

@@ -0,0 +1,211 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import edu.wpi.cscore.VideoSource;
import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.first.networktables.NetworkTableInstance;
/*
JSON format:
{
"team": <team number>,
"ntmode": <"client" or "server", "client" if unspecified>
"cameras": [
{
"name": <camera name>
"path": <path, e.g. "/dev/video0">
"pixel format": <"MJPEG", "YUYV", etc> // optional
"width": <video mode width> // optional
"height": <video mode height> // optional
"fps": <video mode fps> // optional
"brightness": <percentage brightness> // optional
"white balance": <"auto", "hold", value> // optional
"exposure": <"auto", "hold", value> // optional
"properties": [ // optional
{
"name": <property name>
"value": <property value>
}
]
}
]
}
*/
public final class Main {
private static String configFile = "/boot/frc.json";
@SuppressWarnings("MemberName")
public static class CameraConfig {
public String name;
public String path;
public JsonObject config;
}
public static int team;
public static boolean server;
public static List<CameraConfig> cameras = new ArrayList<>();
private Main() {
}
/**
* Report parse error.
*/
public static void parseError(String str) {
System.err.println("config error in '" + configFile + "': " + str);
}
/**
* Read single camera configuration.
*/
public static boolean readCameraConfig(JsonObject config) {
CameraConfig cam = new CameraConfig();
// name
JsonElement nameElement = config.get("name");
if (nameElement == null) {
parseError("could not read camera name");
return false;
}
cam.name = nameElement.getAsString();
// path
JsonElement pathElement = config.get("path");
if (pathElement == null) {
parseError("camera '" + cam.name + "': could not read path");
return false;
}
cam.path = pathElement.getAsString();
cam.config = config;
cameras.add(cam);
return true;
}
/**
* Read configuration file.
*/
@SuppressWarnings("PMD.CyclomaticComplexity")
public static boolean readConfig() {
// parse file
JsonElement top;
try {
top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile)));
} catch (IOException ex) {
System.err.println("could not open '" + configFile + "': " + ex);
return false;
}
// top level must be an object
if (!top.isJsonObject()) {
parseError("must be JSON object");
return false;
}
JsonObject obj = top.getAsJsonObject();
// team number
JsonElement teamElement = obj.get("team");
if (teamElement == null) {
parseError("could not read team number");
return false;
}
team = teamElement.getAsInt();
// ntmode (optional)
if (obj.has("ntmode")) {
String str = obj.get("ntmode").getAsString();
if ("client".equalsIgnoreCase(str)) {
server = false;
} else if ("server".equalsIgnoreCase(str)) {
server = true;
} else {
parseError("could not understand ntmode value '" + str + "'");
}
}
// cameras
JsonElement camerasElement = obj.get("cameras");
if (camerasElement == null) {
parseError("could not read cameras");
return false;
}
JsonArray cameras = camerasElement.getAsJsonArray();
for (JsonElement camera : cameras) {
if (!readCameraConfig(camera.getAsJsonObject())) {
return false;
}
}
return true;
}
/**
* Start running the camera.
*/
public static void startCamera(CameraConfig config) {
System.out.println("Starting camera '" + config.name + "' on " + config.path);
VideoSource camera = CameraServer.getInstance().startAutomaticCapture(
config.name, config.path);
Gson gson = new GsonBuilder().create();
camera.setConfigJson(gson.toJson(config.config));
}
/**
* Main.
*/
public static void main(String... args) {
if (args.length > 0) {
configFile = args[0];
}
// read configuration
if (!readConfig()) {
return;
}
// start NetworkTables
NetworkTableInstance ntinst = NetworkTableInstance.getDefault();
if (server) {
System.out.println("Setting up NetworkTables server");
ntinst.startServer();
} else {
System.out.println("Setting up NetworkTables client for team " + team);
ntinst.startClientTeam(team);
}
// start cameras
for (CameraConfig camera : cameras) {
startCamera(camera);
}
// loop forever
for (;;) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
return;
}
}
}
}

View File

@@ -0,0 +1,190 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include <cstdio>
#include <string>
#include <vector>
#include <networktables/NetworkTableInstance.h>
#include <wpi/StringRef.h>
#include <wpi/json.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include "cameraserver/CameraServer.h"
/*
JSON format:
{
"team": <team number>,
"ntmode": <"client" or "server", "client" if unspecified>
"cameras": [
{
"name": <camera name>
"path": <path, e.g. "/dev/video0">
"pixel format": <"MJPEG", "YUYV", etc> // optional
"width": <video mode width> // optional
"height": <video mode height> // optional
"fps": <video mode fps> // optional
"brightness": <percentage brightness> // optional
"white balance": <"auto", "hold", value> // optional
"exposure": <"auto", "hold", value> // optional
"properties": [ // optional
{
"name": <property name>
"value": <property value>
}
]
}
]
}
*/
#ifdef __RASPBIAN__
static const char* configFile = "/boot/frc.json";
#else
static const char* configFile = "frc.json";
#endif
namespace {
unsigned int team;
bool server = false;
struct CameraConfig {
std::string name;
std::string path;
wpi::json config;
};
std::vector<CameraConfig> cameras;
wpi::raw_ostream& ParseError() {
return wpi::errs() << "config error in '" << configFile << "': ";
}
bool ReadCameraConfig(const wpi::json& config) {
CameraConfig c;
// name
try {
c.name = config.at("name").get<std::string>();
} catch (const wpi::json::exception& e) {
ParseError() << "could not read camera name: " << e.what() << '\n';
return false;
}
// path
try {
c.path = config.at("path").get<std::string>();
} catch (const wpi::json::exception& e) {
ParseError() << "camera '" << c.name
<< "': could not read path: " << e.what() << '\n';
return false;
}
c.config = config;
cameras.emplace_back(std::move(c));
return true;
}
bool ReadConfig() {
// open config file
std::error_code ec;
wpi::raw_fd_istream is(configFile, ec);
if (ec) {
wpi::errs() << "could not open '" << configFile << "': " << ec.message()
<< '\n';
return false;
}
// parse file
wpi::json j;
try {
j = wpi::json::parse(is);
} catch (const wpi::json::parse_error& e) {
ParseError() << "byte " << e.byte << ": " << e.what() << '\n';
return false;
}
// top level must be an object
if (!j.is_object()) {
ParseError() << "must be JSON object\n";
return false;
}
// team number
try {
team = j.at("team").get<unsigned int>();
} catch (const wpi::json::exception& e) {
ParseError() << "could not read team number: " << e.what() << '\n';
return false;
}
// ntmode (optional)
if (j.count("ntmode") != 0) {
try {
auto str = j.at("ntmode").get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("client")) {
server = false;
} else if (s.equals_lower("server")) {
server = true;
} else {
ParseError() << "could not understand ntmode value '" << str << "'\n";
}
} catch (const wpi::json::exception& e) {
ParseError() << "could not read ntmode: " << e.what() << '\n';
}
}
// cameras
try {
for (auto&& camera : j.at("cameras")) {
if (!ReadCameraConfig(camera)) return false;
}
} catch (const wpi::json::exception& e) {
ParseError() << "could not read cameras: " << e.what() << '\n';
return false;
}
return true;
}
void StartCamera(const CameraConfig& config) {
wpi::outs() << "Starting camera '" << config.name << "' on " << config.path
<< '\n';
auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture(
config.name, config.path);
camera.SetConfigJson(config.config);
}
} // namespace
int main(int argc, char* argv[]) {
if (argc >= 2) configFile = argv[1];
// read configuration
if (!ReadConfig()) return EXIT_FAILURE;
// start NetworkTables
auto ntinst = nt::NetworkTableInstance::GetDefault();
if (server) {
wpi::outs() << "Setting up NetworkTables server\n";
ntinst.StartServer();
} else {
wpi::outs() << "Setting up NetworkTables client for team " << team << '\n';
ntinst.StartClientTeam(team);
}
// start cameras
for (auto&& camera : cameras) StartCamera(camera);
// loop forever
for (;;) std::this_thread::sleep_for(std::chrono::seconds(10));
}

View File

@@ -65,6 +65,8 @@ public final class CameraServer {
private final Map<String, VideoSource> m_sources;
private final Map<String, VideoSink> m_sinks;
private final Map<Integer, NetworkTable> m_tables; // indexed by source handle
// source handle indexed by sink handle
private final Map<Integer, Integer> m_fixedSources;
private final NetworkTable m_publishTable;
private final VideoListener m_videoListener; //NOPMD
private final int m_tableListener; //NOPMD
@@ -123,7 +125,7 @@ public final class CameraServer {
}
}
return values.toArray(String[]::new);
return values.toArray(new String[0]);
}
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
@@ -157,14 +159,20 @@ public final class CameraServer {
return values;
}
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP", "PMD.CyclomaticComplexity"})
private synchronized void updateStreamValues() {
// Over all the sinks...
for (VideoSink i : m_sinks.values()) {
int sink = i.getHandle();
// Get the source's subtable (if none exists, we're done)
int source = CameraServerJNI.getSinkSource(sink);
int source;
Integer fixedSource = m_fixedSources.get(sink);
if (fixedSource != null) {
source = fixedSource;
} else {
source = CameraServerJNI.getSinkSource(sink);
}
if (source == 0) {
continue;
}
@@ -295,6 +303,7 @@ public final class CameraServer {
m_defaultUsbDevice = new AtomicInteger();
m_sources = new Hashtable<>();
m_sinks = new Hashtable<>();
m_fixedSources = new Hashtable<>();
m_tables = new Hashtable<>();
m_publishTable = NetworkTableInstance.getDefault().getTable(kPublishName);
m_nextPort = kBasePort;
@@ -537,10 +546,11 @@ public final class CameraServer {
*
* @param camera Camera
*/
public void startAutomaticCapture(VideoSource camera) {
public MjpegServer startAutomaticCapture(VideoSource camera) {
addCamera(camera);
VideoSink server = addServer("serve_" + camera.getName());
MjpegServer server = addServer("serve_" + camera.getName());
server.setSource(camera);
return server;
}
/**
@@ -595,6 +605,21 @@ public final class CameraServer {
return camera;
}
/**
* Adds a virtual camera for switching between two streams. Unlike the
* other addCamera methods, this returns a VideoSink rather than a
* VideoSource. Calling setSource() on the returned object can be used
* to switch the actual source of the stream.
*/
public MjpegServer addSwitchedCamera(String name) {
// create a dummy CvSource
CvSource source = new CvSource(name, VideoMode.PixelFormat.kMJPEG, 160, 120, 30);
MjpegServer server = startAutomaticCapture(source);
m_fixedSources.put(server.getHandle(), source.getHandle());
return server;
}
/**
* Get OpenCV access to the primary camera feed. This allows you to
* get images from the camera for image processing on the roboRIO.

View File

@@ -33,10 +33,11 @@ struct CameraServer::Impl {
void UpdateStreamValues();
wpi::mutex m_mutex;
std::atomic<int> m_defaultUsbDevice;
std::atomic<int> m_defaultUsbDevice{0};
std::string m_primarySourceName;
wpi::StringMap<cs::VideoSource> m_sources;
wpi::StringMap<cs::VideoSink> m_sinks;
wpi::DenseMap<CS_Sink, CS_Source> m_fixedSources;
wpi::DenseMap<CS_Source, std::shared_ptr<nt::NetworkTable>> m_tables;
std::shared_ptr<nt::NetworkTable> m_publishTable;
cs::VideoListener m_videoListener;
@@ -156,7 +157,8 @@ void CameraServer::Impl::UpdateStreamValues() {
CS_Sink sink = i.second.GetHandle();
// Get the source's subtable (if none exists, we're done)
CS_Source source = cs::GetSinkSource(sink, &status);
CS_Source source = m_fixedSources.lookup(sink);
if (source == 0) source = cs::GetSinkSource(sink, &status);
if (source == 0) continue;
auto table = m_tables.lookup(source);
if (table) {
@@ -538,10 +540,21 @@ cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& name,
return camera;
}
void CameraServer::StartAutomaticCapture(const cs::VideoSource& camera) {
cs::MjpegServer CameraServer::AddSwitchedCamera(const wpi::Twine& name) {
// create a dummy CvSource
cs::CvSource source{name, cs::VideoMode::PixelFormat::kMJPEG, 160, 120, 30};
cs::MjpegServer server = StartAutomaticCapture(source);
m_impl->m_fixedSources[server.GetHandle()] = source.GetHandle();
return server;
}
cs::MjpegServer CameraServer::StartAutomaticCapture(
const cs::VideoSource& camera) {
AddCamera(camera);
auto server = AddServer(wpi::Twine("serve_") + camera.GetName());
server.SetSource(camera);
return server;
}
cs::CvSink CameraServer::GetVideo() {

View File

@@ -82,7 +82,7 @@ class CameraServer {
*
* @param camera Camera
*/
void StartAutomaticCapture(const cs::VideoSource& camera);
cs::MjpegServer StartAutomaticCapture(const cs::VideoSource& camera);
/**
* Adds an Axis IP camera.
@@ -173,6 +173,14 @@ class CameraServer {
cs::AxisCamera AddAxisCamera(const wpi::Twine& name,
std::initializer_list<T> hosts);
/**
* Adds a virtual camera for switching between two streams. Unlike the
* other addCamera methods, this returns a VideoSink rather than a
* VideoSource. Calling SetSource() on the returned object can be used
* to switch the actual source of the stream.
*/
cs::MjpegServer AddSwitchedCamera(const wpi::Twine& name);
/**
* Get OpenCV access to the primary camera feed. This allows you to
* get images from the camera for image processing on the roboRIO.

View File

@@ -0,0 +1,25 @@
MACRO(GENERATE_RESOURCES inputDir outputDir prefix namespace outputFiles)
FILE(GLOB inputFiles ${inputDir}/*)
SET(${outputFiles})
FOREACH(input ${inputFiles})
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
IF("${inputBase}" MATCHES "^\\.")
CONTINUE()
ENDIF()
SET(output "${outputDir}/${inputBase}.cpp")
LIST(APPEND ${outputFiles} "${output}")
ADD_CUSTOM_COMMAND(
OUTPUT ${output}
COMMAND ${CMAKE_COMMAND}
"-Dinput=${input}"
"-Doutput=${output}"
"-Dprefix=${prefix}"
"-Dnamespace=${namespace}"
-P "${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake"
MAIN_DEPENDENCY ${input}
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/scripts/GenResource.cmake
VERBATIM
)
ENDFOREACH()
ENDMACRO()

View File

@@ -0,0 +1,23 @@
# Parameters: input output prefix namespace
FILE(READ ${input} fileHex HEX)
STRING(LENGTH "${fileHex}" fileHexSize)
MATH(EXPR fileSize "${fileHexSize} / 2")
GET_FILENAME_COMPONENT(inputBase ${input} NAME)
STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" funcName "${inputBase}")
SET(funcName "GetResource_${funcName}")
FILE(WRITE "${output}" "#include <stddef.h>\n#include <wpi/StringRef.h>\nextern \"C\" {\nstatic const unsigned char contents[] = {")
STRING(REGEX MATCHALL ".." outputData "${fileHex}")
STRING(REGEX REPLACE ";" ", 0x" outputData "${outputData}")
FILE(APPEND "${output}" " 0x${outputData} };\n")
FILE(APPEND "${output}" "const unsigned char* ${prefix}${funcName}(size_t* len) {\n *len = ${fileSize};\n return contents;\n}\n}\n")
IF(NOT namespace STREQUAL "")
FILE(APPEND "${output}" "namespace ${namespace} {\n")
ENDIF()
FILE(APPEND "${output}" "wpi::StringRef ${funcName}() {\n return wpi::StringRef(reinterpret_cast<const char*>(contents), ${fileSize});\n}\n")
IF(NOT namespace STREQUAL "")
FILE(APPEND "${output}" "}\n")
ENDIF()

View File

@@ -19,7 +19,6 @@ if(NOT MSVC)
else()
target_sources(cscore PRIVATE ${cscore_linux_src})
endif()
target_compile_options(cscore PRIVATE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE)
else()
target_sources(cscore PRIVATE ${cscore_windows_src})
target_compile_options(cscore PUBLIC -DNOMINMAX)

View File

@@ -29,6 +29,7 @@ ext {
}
exportedHeaders {
srcDirs 'src/main/native/include'
include '**/*.h'
}
}
cscoreMacCpp(CppSourceSet) {
@@ -38,6 +39,7 @@ ext {
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
@@ -50,6 +52,7 @@ ext {
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
@@ -62,6 +65,7 @@ ext {
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
@@ -94,11 +98,31 @@ model {
x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVbad_cast',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure']
'_TI5?AVfailure', '==']
x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
'_CT??_R0?AVbad_cast',
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure']
'_TI5?AVfailure', '==']
}
cscoreJNI(ExportsConfig) {
x86SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('CS_')) {
retList << symbol
}
}
return retList
}
x64SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('CS_')) {
retList << symbol
}
}
return retList
}
}
}
components {

View File

@@ -17,6 +17,12 @@ int main() {
for (const auto& caminfo : cs::EnumerateUsbCameras(&status)) {
wpi::outs() << caminfo.dev << ": " << caminfo.path << " (" << caminfo.name
<< ")\n";
if (!caminfo.otherPaths.empty()) {
wpi::outs() << "Other device paths:\n";
for (auto&& path : caminfo.otherPaths)
wpi::outs() << " " << path << '\n';
}
cs::UsbCamera camera{"usbcam", caminfo.dev};
wpi::outs() << "Properties:\n";

View File

@@ -89,6 +89,8 @@ public class CameraServerJNI {
public static native boolean setSourcePixelFormat(int source, int pixelFormat);
public static native boolean setSourceResolution(int source, int width, int height);
public static native boolean setSourceFPS(int source, int fps);
public static native boolean setSourceConfigJson(int source, String config);
public static native String getSourceConfigJson(int source);
public static native VideoMode[] enumerateSourceVideoModes(int source);
public static native int[] enumerateSourceSinks(int source);
public static native int copySource(int source);
@@ -110,6 +112,7 @@ public class CameraServerJNI {
// UsbCamera Source Functions
//
public static native String getUsbCameraPath(int source);
public static native UsbCameraInfo getUsbCameraInfo(int source);
//
// HttpCamera Source Functions
@@ -144,6 +147,8 @@ public class CameraServerJNI {
public static native String getSinkDescription(int sink);
public static native int getSinkProperty(int sink, String name);
public static native int[] enumerateSinkProperties(int sink);
public static native boolean setSinkConfigJson(int sink, String config);
public static native String getSinkConfigJson(int sink);
public static native void setSinkSource(int sink, int source);
public static native int getSinkSourceProperty(int sink, String name);
public static native int getSinkSource(int sink);

View File

@@ -60,7 +60,7 @@ public class MjpegServer extends VideoSink {
* @param width width, 0 for unspecified
* @param height height, 0 for unspecified
*/
void setResolution(int width, int height) {
public void setResolution(int width, int height) {
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "width"), width);
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "height"), height);
}
@@ -72,7 +72,7 @@ public class MjpegServer extends VideoSink {
*
* @param fps FPS, 0 for unspecified
*/
void setFPS(int fps) {
public void setFPS(int fps) {
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "fps"), fps);
}
@@ -85,7 +85,7 @@ public class MjpegServer extends VideoSink {
*
* @param quality JPEG compression quality (0-100), -1 for unspecified
*/
void setCompression(int quality) {
public void setCompression(int quality) {
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "compression"),
quality);
}
@@ -97,7 +97,7 @@ public class MjpegServer extends VideoSink {
*
* @param quality JPEG compression quality (0-100)
*/
void setDefaultCompression(int quality) {
public void setDefaultCompression(int quality) {
CameraServerJNI.setProperty(CameraServerJNI.getSinkProperty(m_handle, "default_compression"),
quality);
}

View File

@@ -47,6 +47,13 @@ public class UsbCamera extends VideoCamera {
return CameraServerJNI.getUsbCameraPath(m_handle);
}
/**
* Get the full camera information for the device.
*/
public UsbCameraInfo getInfo() {
return CameraServerJNI.getUsbCameraInfo(m_handle);
}
/**
* Set how verbose the camera connection messages are.
*

View File

@@ -17,11 +17,14 @@ public class UsbCameraInfo {
* @param dev Device number (e.g. N in '/dev/videoN' on Linux)
* @param path Path to device if available (e.g. '/dev/video0' on Linux)
* @param name Vendor/model name of the camera as provided by the USB driver
* @param otherPaths Other path aliases to device
*/
public UsbCameraInfo(int dev, String path, String name) {
@SuppressWarnings("PMD.ArrayIsStoredDirectly")
public UsbCameraInfo(int dev, String path, String name, String[] otherPaths) {
this.dev = dev;
this.path = path;
this.name = name;
this.otherPaths = otherPaths;
}
/**
@@ -41,4 +44,10 @@ public class UsbCameraInfo {
*/
@SuppressWarnings("MemberName")
public String name;
/**
* Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux).
*/
@SuppressWarnings("MemberName")
public String[] otherPaths;
}

View File

@@ -133,6 +133,38 @@ public class VideoSink implements AutoCloseable {
return rv;
}
/**
* Set properties from a JSON configuration string.
*
* <p>The format of the JSON input is:
*
* <pre>
* {
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
public boolean setConfigJson(String config) {
return CameraServerJNI.setSinkConfigJson(m_handle, config);
}
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
public String getConfigJson() {
return CameraServerJNI.getSinkConfigJson(m_handle);
}
/**
* Configure which source should provide frames to this sink. Each sink
* can accept frames from only a single source, but a single source can

View File

@@ -270,6 +270,45 @@ public class VideoSource implements AutoCloseable {
return CameraServerJNI.setSourceFPS(m_handle, fps);
}
/**
* Set video mode and properties from a JSON configuration string.
*
* <p>The format of the JSON input is:
*
* <pre>
* {
* "pixel format": "MJPEG", "YUYV", etc
* "width": video mode width
* "height": video mode height
* "fps": video mode fps
* "brightness": percentage brightness
* "white balance": "auto", "hold", or value
* "exposure": "auto", "hold", or value
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
public boolean setConfigJson(String config) {
return CameraServerJNI.setSourceConfigJson(m_handle, config);
}
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
public String getConfigJson() {
return CameraServerJNI.getSourceConfigJson(m_handle);
}
/**
* Get the actual FPS.
*

View File

@@ -30,6 +30,12 @@ HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
HttpCameraImpl::~HttpCameraImpl() {
m_active = false;
// force wakeup of monitor thread
m_monitorCond.notify_one();
// join monitor thread
if (m_monitorThread.joinable()) m_monitorThread.join();
// Close file if it's open
{
std::lock_guard<wpi::mutex> lock(m_mutex);
@@ -54,6 +60,31 @@ void HttpCameraImpl::Start() {
// Kick off the stream and settings threads
m_streamThread = std::thread(&HttpCameraImpl::StreamThreadMain, this);
m_settingsThread = std::thread(&HttpCameraImpl::SettingsThreadMain, this);
m_monitorThread = std::thread(&HttpCameraImpl::MonitorThreadMain, this);
}
void HttpCameraImpl::MonitorThreadMain() {
while (m_active) {
std::unique_lock<wpi::mutex> lock(m_mutex);
// sleep for 1 second between checks
m_monitorCond.wait_for(lock, std::chrono::seconds(1),
[=] { return !m_active; });
if (!m_active) break;
// check to see if we got any frames, and close the stream if not
// (this will result in an error at the read point, and ultimately
// a reconnect attempt)
if (m_streamConn && m_frameCount == 0) {
SWARNING("Monitor detected stream hung, disconnecting");
m_streamConn->stream->close();
}
// reset the frame counter
m_frameCount = 0;
}
SDEBUG("Monitor Thread exiting");
}
void HttpCameraImpl::StreamThreadMain() {
@@ -86,6 +117,10 @@ void HttpCameraImpl::StreamThreadMain() {
// stream
DeviceStream(conn->is, boundary);
{
std::unique_lock<wpi::mutex> lock(m_mutex);
m_streamConn = nullptr;
}
}
SDEBUG("Camera Thread exiting");
@@ -120,6 +155,7 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
// update m_streamConn
{
std::lock_guard<wpi::mutex> lock(m_mutex);
m_frameCount = 1; // avoid a race with monitor thread
m_streamConn = std::move(connPtr);
}
@@ -229,6 +265,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
}
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
wpi::Now());
++m_frameCount;
return true;
}
@@ -246,6 +283,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
image->width = width;
image->height = height;
PutFrame(std::move(image), wpi::Now());
++m_frameCount;
return true;
}

View File

@@ -111,10 +111,14 @@ class HttpCameraImpl : public SourceImpl {
void SettingsThreadMain();
void DeviceSendSettings(wpi::HttpRequest& req);
// The monitor thread
void MonitorThreadMain();
std::atomic_bool m_connected{false};
std::atomic_bool m_active{true}; // set to false to terminate thread
std::thread m_streamThread;
std::thread m_settingsThread;
std::thread m_monitorThread;
//
// Variables protected by m_mutex
@@ -130,6 +134,8 @@ class HttpCameraImpl : public SourceImpl {
size_t m_nextLocation{0};
int m_prefLocation{-1}; // preferred location
std::atomic_int m_frameCount{0};
wpi::condition_variable m_sinkEnabledCond;
wpi::StringMap<wpi::SmallString<16>> m_settings;
@@ -137,6 +143,8 @@ class HttpCameraImpl : public SourceImpl {
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
std::atomic_bool m_streamSettingsUpdated{false};
wpi::condition_variable m_monitorCond;
};
class AxisCameraImpl : public HttpCameraImpl {

View File

@@ -67,7 +67,8 @@ static const char* startRootPage =
"</head><body>\n"
"<div class=\"stream\">\n"
"<img src=\"/stream.mjpg\" /><p />\n"
"<a href=\"/settings.json\">Settings JSON</a>\n"
"<a href=\"/settings.json\">Settings JSON</a> |\n"
"<a href=\"/config.json\">Source Config JSON</a>\n"
"</div>\n"
"<div class=\"settings\">\n";
static const char* endRootPage = "</div></body></html>";
@@ -110,13 +111,13 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
void StartStream() {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_source->EnableSink();
if (m_source) m_source->EnableSink();
m_streaming = true;
}
void StopStream() {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_source->DisableSink();
if (m_source) m_source->DisableSink();
m_streaming = false;
}
};
@@ -334,13 +335,14 @@ bool MjpegServerImpl::ConnThread::ProcessCommand(wpi::raw_ostream& os,
void MjpegServerImpl::ConnThread::SendHTMLHeadTitle(
wpi::raw_ostream& os) const {
os << "<html><head><title>" << m_name << " CameraServer</title>";
os << "<html><head><title>" << m_name << " CameraServer</title>"
<< "<meta charset=\"UTF-8\">";
}
// Send the root html file with controls for all the settable properties.
void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
if (header) SendHeader(os, 200, "OK", "text/html");
SendHTMLHeadTitle(os);
os << startRootPage;
@@ -412,6 +414,15 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
}
}
status = 0;
auto info = GetUsbCameraInfo(Instance::GetInstance().FindSource(source).first,
&status);
if (status == CS_OK) {
os << "<p>USB device path: " << info.path << '\n';
for (auto&& path : info.otherPaths)
os << "<p>Alternate device path: " << path << '\n';
}
os << "<p>Supported Video Modes:</p>\n";
os << "<table cols=\"4\" style=\"border: 1px solid black\">\n";
os << "<tr><th>Pixel Format</th>"
@@ -453,7 +464,7 @@ void MjpegServerImpl::ConnThread::SendHTML(wpi::raw_ostream& os,
// Send a JSON file which is contains information about the source parameters.
void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
SourceImpl& source, bool header) {
if (header) SendHeader(os, 200, "OK", "application/x-javascript");
if (header) SendHeader(os, 200, "OK", "application/json");
os << "{\n\"controls\": [\n";
wpi::SmallVector<int, 32> properties_vec;
@@ -632,8 +643,9 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
Frame::Time lastFrameTime = 0;
Frame::Time timePerFrame = 0;
if (m_fps != 0) timePerFrame = 1000000.0 / m_fps;
// Allow fudge factor of 1 ms in frame rate
if (timePerFrame >= 1000) timePerFrame -= 1000;
Frame::Time averageFrameTime = 0;
Frame::Time averagePeriod = 1000000; // 1 second window
if (averagePeriod < timePerFrame) averagePeriod = timePerFrame * 10;
StartStream();
while (m_active && !os.has_error()) {
@@ -654,10 +666,26 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
continue;
}
if (frame.GetTime() < (lastFrameTime + timePerFrame)) {
// Limit FPS; sleep for 10 ms so we don't consume all processor time
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
auto thisFrameTime = frame.GetTime();
if (thisFrameTime != 0 && timePerFrame != 0 && lastFrameTime != 0) {
Frame::Time deltaTime = thisFrameTime - lastFrameTime;
// drop frame if it is early compared to the desired frame rate AND
// the current average is higher than the desired average
if (deltaTime < timePerFrame && averageFrameTime < timePerFrame) {
// sleep for 1 ms so we don't consume all processor time
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
// update average
if (averageFrameTime != 0) {
averageFrameTime =
averageFrameTime * (averagePeriod - timePerFrame) / averagePeriod +
deltaTime * timePerFrame / averagePeriod;
} else {
averageFrameTime = deltaTime;
}
}
int width = m_width != 0 ? m_width : frame.GetOriginalWidth();
@@ -694,7 +722,7 @@ void MjpegServerImpl::ConnThread::SendStream(wpi::raw_socket_ostream& os) {
// print the individual mimetype and the length
// sending the content-length fixes random stream disruption observed
// with firefox
lastFrameTime = frame.GetTime();
lastFrameTime = thisFrameTime;
double timestamp = lastFrameTime / 1000000.0;
header.clear();
oss << "\r\n--" BOUNDARY "\r\n"
@@ -728,7 +756,7 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
return;
}
enum { kCommand, kStream, kGetSettings, kRootPage } kind;
enum { kCommand, kStream, kGetSettings, kGetSourceConfig, kRootPage } kind;
wpi::StringRef parameters;
size_t pos;
@@ -748,6 +776,9 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
} else if (req.find("GET /settings") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSettings;
} else if (req.find("GET /config") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSourceConfig;
} else if (req.find("GET /input") != wpi::StringRef::npos &&
req.find(".json") != wpi::StringRef::npos) {
kind = kGetSettings;
@@ -806,6 +837,17 @@ void MjpegServerImpl::ConnThread::ProcessRequest() {
else
SendError(os, 404, "Resource not found");
break;
case kGetSourceConfig:
SDEBUG("request for JSON file");
if (auto source = GetSource()) {
SendHeader(os, 200, "OK", "application/json");
CS_Status status = CS_OK;
os << source->GetConfigJson(&status);
os.flush();
} else {
SendError(os, 404, "Resource not found");
}
break;
case kRootPage:
SDEBUG("request for root page");
SendHeader(os, 200, "OK", "text/html");

View File

@@ -7,6 +7,11 @@
#include "PropertyContainer.h"
#include <wpi/Logger.h>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/json.h>
using namespace cs;
int PropertyContainer::GetPropertyIndex(const wpi::Twine& name) const {
@@ -204,3 +209,74 @@ bool PropertyContainer::CacheProperties(CS_Status* status) const {
m_properties_cached = true;
return true;
}
bool PropertyContainer::SetPropertiesJson(const wpi::json& config,
wpi::Logger& logger,
wpi::StringRef logName,
CS_Status* status) {
for (auto&& prop : config) {
std::string name;
try {
name = prop.at("name").get<std::string>();
} catch (const wpi::json::exception& e) {
WPI_WARNING(logger,
logName << ": SetConfigJson: could not read property name: "
<< e.what());
continue;
}
int n = GetPropertyIndex(name);
try {
auto& v = prop.at("value");
if (v.is_string()) {
std::string val = v.get<std::string>();
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
<< name << "' to '" << val << '\'');
SetStringProperty(n, val, status);
} else if (v.is_boolean()) {
bool val = v.get<bool>();
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
<< name << "' to " << val);
SetProperty(n, val, status);
} else {
int val = v.get<int>();
WPI_INFO(logger, logName << ": SetConfigJson: setting property '"
<< name << "' to " << val);
SetProperty(n, val, status);
}
} catch (const wpi::json::exception& e) {
WPI_WARNING(logger,
logName << ": SetConfigJson: could not read property value: "
<< e.what());
continue;
}
}
return true;
}
wpi::json PropertyContainer::GetPropertiesJsonObject(CS_Status* status) {
wpi::json j;
wpi::SmallVector<int, 32> propVec;
for (int p : EnumerateProperties(propVec, status)) {
wpi::json prop;
wpi::SmallString<128> strBuf;
prop.emplace("name", GetPropertyName(p, strBuf, status));
switch (GetPropertyKind(p)) {
case CS_PROP_BOOLEAN:
prop.emplace("value", static_cast<bool>(GetProperty(p, status)));
break;
case CS_PROP_INTEGER:
case CS_PROP_ENUM:
prop.emplace("value", GetProperty(p, status));
break;
case CS_PROP_STRING:
prop.emplace("value", GetStringProperty(p, strBuf, status));
break;
default:
continue;
}
j.emplace_back(prop);
}
return j;
}

View File

@@ -24,6 +24,11 @@
#include "PropertyImpl.h"
#include "cscore_cpp.h"
namespace wpi {
class Logger;
class json;
} // namespace wpi
namespace cs {
class PropertyContainer {
@@ -50,6 +55,10 @@ class PropertyContainer {
std::vector<std::string> GetEnumPropertyChoices(int property,
CS_Status* status) const;
bool SetPropertiesJson(const wpi::json& config, wpi::Logger& logger,
wpi::StringRef logName, CS_Status* status);
wpi::json GetPropertiesJsonObject(CS_Status* status);
protected:
// Get a property; must be called with m_mutex held.
PropertyImpl* GetProperty(int property) {

View File

@@ -7,6 +7,8 @@
#include "SinkImpl.h"
#include <wpi/json.h>
#include "Instance.h"
#include "Notifier.h"
#include "SourceImpl.h"
@@ -102,6 +104,43 @@ wpi::StringRef SinkImpl::GetError(wpi::SmallVectorImpl<char>& buf) const {
return wpi::StringRef{buf.data(), buf.size()};
}
bool SinkImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
wpi::json j;
try {
j = wpi::json::parse(config);
} catch (const wpi::json::parse_error& e) {
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
<< e.what());
*status = CS_PROPERTY_WRITE_FAILED;
return false;
}
return SetConfigJson(j, status);
}
bool SinkImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
if (config.count("properties") != 0)
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
return true;
}
std::string SinkImpl::GetConfigJson(CS_Status* status) {
std::string rv;
wpi::raw_string_ostream os(rv);
GetConfigJsonObject(status).dump(os, 4);
os.flush();
return rv;
}
wpi::json SinkImpl::GetConfigJsonObject(CS_Status* status) {
wpi::json j;
wpi::json props = GetPropertiesJsonObject(status);
if (props.is_array()) j.emplace("properties", props);
return j;
}
void SinkImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,

View File

@@ -18,6 +18,10 @@
#include "SourceImpl.h"
namespace wpi {
class json;
} // namespace wpi
namespace cs {
class Frame;
@@ -51,6 +55,11 @@ class SinkImpl : public PropertyContainer {
std::string GetError() const;
wpi::StringRef GetError(wpi::SmallVectorImpl<char>& buf) const;
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
std::string GetConfigJson(CS_Status* status);
virtual wpi::json GetConfigJsonObject(CS_Status* status);
protected:
// PropertyContainer implementation
void NotifyPropertyCreated(int propIndex, PropertyImpl& prop) override;

View File

@@ -11,6 +11,7 @@
#include <cstring>
#include <wpi/STLExtras.h>
#include <wpi/json.h>
#include <wpi/timestamp.h>
#include "Log.h"
@@ -161,6 +162,221 @@ bool SourceImpl::SetFPS(int fps, CS_Status* status) {
return SetVideoMode(mode, status);
}
bool SourceImpl::SetConfigJson(wpi::StringRef config, CS_Status* status) {
wpi::json j;
try {
j = wpi::json::parse(config);
} catch (const wpi::json::parse_error& e) {
SWARNING("SetConfigJson: parse error at byte " << e.byte << ": "
<< e.what());
*status = CS_PROPERTY_WRITE_FAILED;
return false;
}
return SetConfigJson(j, status);
}
bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
VideoMode mode;
// pixel format
if (config.count("pixel format") != 0) {
try {
auto str = config.at("pixel format").get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("mjpeg")) {
mode.pixelFormat = cs::VideoMode::kMJPEG;
} else if (s.equals_lower("yuyv")) {
mode.pixelFormat = cs::VideoMode::kYUYV;
} else if (s.equals_lower("rgb565")) {
mode.pixelFormat = cs::VideoMode::kRGB565;
} else if (s.equals_lower("bgr")) {
mode.pixelFormat = cs::VideoMode::kBGR;
} else if (s.equals_lower("gray")) {
mode.pixelFormat = cs::VideoMode::kGray;
} else {
SWARNING("SetConfigJson: could not understand pixel format value '"
<< str << '\'');
}
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read pixel format: " << e.what());
}
}
// width
if (config.count("width") != 0) {
try {
mode.width = config.at("width").get<unsigned int>();
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read width: " << e.what());
}
}
// height
if (config.count("height") != 0) {
try {
mode.height = config.at("height").get<unsigned int>();
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read height: " << e.what());
}
}
// fps
if (config.count("fps") != 0) {
try {
mode.fps = config.at("fps").get<unsigned int>();
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read fps: " << e.what());
}
}
// if all of video mode is set, use SetVideoMode, otherwise piecemeal it
if (mode.pixelFormat != VideoMode::kUnknown && mode.width != 0 &&
mode.height != 0 && mode.fps != 0) {
SINFO("SetConfigJson: setting video mode to pixelFormat "
<< mode.pixelFormat << ", width " << mode.width << ", height "
<< mode.height << ", fps " << mode.fps);
SetVideoMode(mode, status);
} else {
if (mode.pixelFormat != cs::VideoMode::kUnknown) {
SINFO("SetConfigJson: setting pixelFormat " << mode.pixelFormat);
SetPixelFormat(static_cast<cs::VideoMode::PixelFormat>(mode.pixelFormat),
status);
}
if (mode.width != 0 && mode.height != 0) {
SINFO("SetConfigJson: setting width " << mode.width << ", height "
<< mode.height);
SetResolution(mode.width, mode.height, status);
}
if (mode.fps != 0) {
SINFO("SetConfigJson: setting fps " << mode.fps);
SetFPS(mode.fps, status);
}
}
// brightness
if (config.count("brightness") != 0) {
try {
int val = config.at("brightness").get<int>();
SINFO("SetConfigJson: setting brightness to " << val);
SetBrightness(val, status);
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read brightness: " << e.what());
}
}
// white balance
if (config.count("white balance") != 0) {
try {
auto& setting = config.at("white balance");
if (setting.is_string()) {
auto str = setting.get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("auto")) {
SINFO("SetConfigJson: setting white balance to auto");
SetWhiteBalanceAuto(status);
} else if (s.equals_lower("hold")) {
SINFO("SetConfigJson: setting white balance to hold current");
SetWhiteBalanceHoldCurrent(status);
} else {
SWARNING("SetConfigJson: could not understand white balance value '"
<< str << '\'');
}
} else {
int val = setting.get<int>();
SINFO("SetConfigJson: setting white balance to " << val);
SetWhiteBalanceManual(val, status);
}
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read white balance: " << e.what());
}
}
// exposure
if (config.count("exposure") != 0) {
try {
auto& setting = config.at("exposure");
if (setting.is_string()) {
auto str = setting.get<std::string>();
wpi::StringRef s(str);
if (s.equals_lower("auto")) {
SINFO("SetConfigJson: setting exposure to auto");
SetExposureAuto(status);
} else if (s.equals_lower("hold")) {
SINFO("SetConfigJson: setting exposure to hold current");
SetExposureHoldCurrent(status);
} else {
SWARNING("SetConfigJson: could not understand exposure value '"
<< str << '\'');
}
} else {
int val = setting.get<int>();
SINFO("SetConfigJson: setting exposure to " << val);
SetExposureManual(val, status);
}
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read exposure: " << e.what());
}
}
// properties
if (config.count("properties") != 0)
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
return true;
}
std::string SourceImpl::GetConfigJson(CS_Status* status) {
std::string rv;
wpi::raw_string_ostream os(rv);
GetConfigJsonObject(status).dump(os, 4);
os.flush();
return rv;
}
wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
wpi::json j;
// pixel format
wpi::StringRef pixelFormat;
switch (m_mode.pixelFormat) {
case VideoMode::kMJPEG:
pixelFormat = "mjpeg";
break;
case VideoMode::kYUYV:
pixelFormat = "yuyv";
break;
case VideoMode::kRGB565:
pixelFormat = "rgb565";
break;
case VideoMode::kBGR:
pixelFormat = "bgr";
break;
case VideoMode::kGray:
pixelFormat = "gray";
break;
default:
break;
}
if (!pixelFormat.empty()) j.emplace("pixel format", pixelFormat);
// width
if (m_mode.width != 0) j.emplace("width", m_mode.width);
// height
if (m_mode.height != 0) j.emplace("height", m_mode.height);
// fps
if (m_mode.fps != 0) j.emplace("fps", m_mode.fps);
// TODO: output brightness, white balance, and exposure?
// properties
wpi::json props = GetPropertiesJsonObject(status);
if (props.is_array()) j.emplace("properties", props);
return j;
}
std::vector<VideoMode> SourceImpl::EnumerateVideoModes(
CS_Status* status) const {
if (!m_properties_cached && !CacheProperties(status))

View File

@@ -27,6 +27,10 @@
#include "PropertyContainer.h"
#include "cscore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace cs {
class Notifier;
@@ -127,6 +131,11 @@ class SourceImpl : public PropertyContainer {
virtual bool SetResolution(int width, int height, CS_Status* status);
virtual bool SetFPS(int fps, CS_Status* status);
bool SetConfigJson(wpi::StringRef config, CS_Status* status);
virtual bool SetConfigJson(const wpi::json& config, CS_Status* status);
std::string GetConfigJson(CS_Status* status);
virtual wpi::json GetConfigJsonObject(CS_Status* status);
std::vector<VideoMode> EnumerateVideoModes(CS_Status* status) const;
std::unique_ptr<Image> AllocImage(VideoMode::PixelFormat pixelFormat,

View File

@@ -12,6 +12,25 @@
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::CheckedMalloc(in.otherPaths.size() * sizeof(char*)));
out->otherPathsCount = in.otherPaths.size();
for (size_t i = 0; i < in.otherPaths.size(); ++i)
out->otherPaths[i] = cs::ConvertToC(in.otherPaths[i]);
}
static void FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
std::free(info->path);
std::free(info->name);
for (int i = 0; i < info->otherPathsCount; ++i)
std::free(info->otherPaths[i]);
std::free(info->otherPaths);
}
extern "C" {
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
@@ -27,26 +46,34 @@ char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(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::CheckedMalloc(sizeof(CS_UsbCameraInfo)));
ConvertToC(out, info);
return out;
}
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
auto cameras = cs::EnumerateUsbCameras(status);
CS_UsbCameraInfo* out = static_cast<CS_UsbCameraInfo*>(
wpi::CheckedMalloc(cameras.size() * sizeof(CS_UsbCameraInfo)));
*count = cameras.size();
for (size_t i = 0; i < cameras.size(); ++i) {
out[i].dev = cameras[i].dev;
out[i].path = ConvertToC(cameras[i].path);
out[i].name = ConvertToC(cameras[i].name);
}
for (size_t i = 0; i < cameras.size(); ++i) ConvertToC(&out[i], cameras[i]);
return out;
}
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {
if (!cameras) return;
for (int i = 0; i < count; ++i) {
std::free(cameras[i].path);
std::free(cameras[i].name);
}
for (int i = 0; i < count; ++i) FreeUsbCameraInfo(&cameras[i]);
std::free(cameras);
}
void CS_FreeUsbCameraInfo(CS_UsbCameraInfo* info) {
if (!info) return;
FreeUsbCameraInfo(info);
std::free(info);
}
} // extern "C"

View File

@@ -170,6 +170,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_Status* status) {
return cs::SetSourceConfigJson(source, config, status);
}
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status) {
return cs::ConvertToC(cs::GetSourceConfigJson(source, status));
}
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status) {
auto vec = cs::EnumerateSourceVideoModes(source, status);
@@ -268,6 +277,15 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
return out;
}
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
CS_Status* status) {
return cs::SetSinkConfigJson(sink, config, status);
}
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
return cs::ConvertToC(cs::GetSinkConfigJson(sink, status));
}
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
return cs::SetSinkSource(sink, source, status);
}

View File

@@ -9,6 +9,7 @@
#include <wpi/SmallString.h>
#include <wpi/hostname.h>
#include <wpi/json.h>
#include "Handle.h"
#include "Instance.h"
@@ -324,6 +325,44 @@ bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
return data->source->SetFPS(fps, status);
}
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetConfigJson(config, status);
}
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->source->SetConfigJson(config, status);
}
std::string GetSourceConfigJson(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->source->GetConfigJson(status);
}
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::json{};
}
return data->source->GetConfigJsonObject(status);
}
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status) {
auto data = Instance::GetInstance().GetSource(source);
@@ -525,6 +564,43 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
return vec;
}
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->sink->SetConfigJson(config, status);
}
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->sink->SetConfigJson(config, status);
}
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->sink->GetConfigJson(status);
}
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::json{};
}
return data->sink->GetConfigJsonObject(status);
}
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {

View File

@@ -7,8 +7,20 @@
#include "cscore_oo.h"
#include <wpi/json.h>
using namespace cs;
wpi::json VideoSource::GetConfigJsonObject() const {
m_status = 0;
return GetSourceConfigJsonObject(m_handle, &m_status);
}
wpi::json VideoSink::GetConfigJsonObject() const {
m_status = 0;
return GetSinkConfigJsonObject(m_handle, &m_status);
}
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
wpi::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;

View File

@@ -185,11 +185,14 @@ static inline bool CheckStatus(JNIEnv* env, CS_Status status) {
static jobject MakeJObject(JNIEnv* env, const cs::UsbCameraInfo& info) {
static jmethodID constructor = env->GetMethodID(
usbCameraInfoCls, "<init>", "(ILjava/lang/String;Ljava/lang/String;)V");
usbCameraInfoCls, "<init>",
"(ILjava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
JLocal<jstring> path(env, MakeJString(env, info.path));
JLocal<jstring> name(env, MakeJString(env, info.name));
JLocal<jobjectArray> otherPaths(env, MakeJStringArray(env, info.otherPaths));
return env->NewObject(usbCameraInfoCls, constructor,
static_cast<jint>(info.dev), path.obj(), name.obj());
static_cast<jint>(info.dev), path.obj(), name.obj(),
otherPaths.obj());
}
static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
@@ -751,6 +754,36 @@ Java_edu_wpi_cscore_CameraServerJNI_setSourceFPS
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: setSourceConfigJson
* Signature: (ILjava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_cscore_CameraServerJNI_setSourceConfigJson
(JNIEnv* env, jclass, jint source, jstring config)
{
CS_Status status = 0;
auto val = cs::SetSourceConfigJson(source, JStringRef{env, config}, &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSourceConfigJson
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_cscore_CameraServerJNI_getSourceConfigJson
(JNIEnv* env, jclass, jint source)
{
CS_Status status = 0;
auto val = cs::GetSourceConfigJson(source, &status);
CheckStatus(env, status);
return MakeJString(env, val);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: enumerateSourceVideoModes
@@ -945,6 +978,21 @@ Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraPath
return MakeJString(env, str);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getUsbCameraInfo
* Signature: (I)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL
Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraInfo
(JNIEnv* env, jclass, jint source)
{
CS_Status status = 0;
auto info = cs::GetUsbCameraInfo(source, &status);
if (!CheckStatus(env, status)) return nullptr;
return MakeJObject(env, info);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getHttpCameraKind
@@ -1244,6 +1292,36 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateSinkProperties
return MakeJIntArray(env, arr);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: setSinkConfigJson
* Signature: (ILjava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_cscore_CameraServerJNI_setSinkConfigJson
(JNIEnv* env, jclass, jint source, jstring config)
{
CS_Status status = 0;
auto val = cs::SetSinkConfigJson(source, JStringRef{env, config}, &status);
CheckStatus(env, status);
return val;
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: getSinkConfigJson
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_edu_wpi_cscore_CameraServerJNI_getSinkConfigJson
(JNIEnv* env, jclass, jint source)
{
CS_Status status = 0;
auto val = cs::GetSinkConfigJson(source, &status);
CheckStatus(env, status);
return MakeJString(env, val);
}
/*
* Class: edu_wpi_cscore_CameraServerJNI
* Method: setSinkSource

View File

@@ -69,7 +69,8 @@ enum CS_StatusValue {
CS_SOURCE_IS_DISCONNECTED = -2005,
CS_EMPTY_VALUE = -2006,
CS_BAD_URL = -2007,
CS_TELEMETRY_NOT_ENABLED = -2008
CS_TELEMETRY_NOT_ENABLED = -2008,
CS_UNSUPPORTED_MODE = -2009
};
/**
@@ -222,6 +223,17 @@ struct CS_Event {
const char* valueStr;
};
/**
* USB camera infomation
*/
typedef struct CS_UsbCameraInfo {
int dev;
char* path;
char* name;
int otherPathsCount;
char** otherPaths;
} CS_UsbCameraInfo;
/**
* @defgroup cscore_property_cfunc Property Functions
* @{
@@ -289,6 +301,9 @@ 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_Status* status);
char* CS_GetSourceConfigJson(CS_Source source, CS_Status* status);
CS_VideoMode* CS_EnumerateSourceVideoModes(CS_Source source, int* count,
CS_Status* status);
CS_Sink* CS_EnumerateSourceSinks(CS_Source source, int* count,
@@ -318,6 +333,7 @@ void CS_SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @{
*/
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status);
CS_UsbCameraInfo* CS_GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */
/**
@@ -377,6 +393,9 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property CS_GetSinkSourceProperty(CS_Sink sink, const char* name,
CS_Status* status);
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
CS_Status* status);
char* CS_GetSinkConfigJson(CS_Sink sink, 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);
@@ -452,15 +471,6 @@ void CS_Shutdown(void);
* @{
*/
/**
* USB camera infomation
*/
typedef struct CS_UsbCameraInfo {
int dev;
char* path;
char* name;
} CS_UsbCameraInfo;
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
@@ -472,6 +482,7 @@ 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);

View File

@@ -25,6 +25,10 @@ namespace cv {
class Mat;
} // namespace cv
namespace wpi {
class json;
} // namespace wpi
/** CameraServer (cscore) namespace */
namespace cs {
@@ -43,11 +47,13 @@ namespace cs {
*/
struct UsbCameraInfo {
/** Device number (e.g. N in '/dev/videoN' on Linux) */
int dev;
int dev = -1;
/** Path to device if available (e.g. '/dev/video0' on Linux) */
std::string path;
/** Vendor/model name of the camera as provided by the USB driver */
std::string name;
/** Other path aliases to device (e.g. '/dev/v4l/by-id/...' etc on Linux) */
std::vector<std::string> otherPaths;
};
/**
@@ -75,6 +81,13 @@ struct VideoMode : public CS_VideoMode {
fps = fps_;
}
explicit operator bool() const { return pixelFormat == kUnknown; }
bool operator==(const VideoMode& other) const {
return pixelFormat == other.pixelFormat && width == other.width &&
height == other.height && fps == other.fps;
}
bool operator!=(const VideoMode& other) const { return !(*this == other); }
};
/**
@@ -221,6 +234,12 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
bool SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status);
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status);
bool SetSourceConfigJson(CS_Source source, wpi::StringRef config,
CS_Status* status);
bool SetSourceConfigJson(CS_Source source, const wpi::json& config,
CS_Status* status);
std::string GetSourceConfigJson(CS_Source source, CS_Status* status);
wpi::json GetSourceConfigJsonObject(CS_Source source, CS_Status* status);
std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
CS_Status* status);
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(CS_Source source,
@@ -250,6 +269,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status);
* @{
*/
std::string GetUsbCameraPath(CS_Source source, CS_Status* status);
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status);
/** @} */
/**
@@ -312,6 +332,11 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
CS_Status* status);
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status);
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
CS_Status* status);
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status);
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status);
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status);
CS_Sink CopySink(CS_Sink sink, CS_Status* status);
void ReleaseSink(CS_Sink sink, CS_Status* status);

View File

@@ -253,6 +253,56 @@ class VideoSource {
*/
bool SetFPS(int fps);
/**
* Set video mode and properties from a JSON configuration string.
*
* The format of the JSON input is:
*
* <pre>
* {
* "pixel format": "MJPEG", "YUYV", etc
* "width": video mode width
* "height": video mode height
* "fps": video mode fps
* "brightness": percentage brightness
* "white balance": "auto", "hold", or value
* "exposure": "auto", "hold", or value
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(wpi::StringRef config);
/**
* Set video mode and properties from a JSON configuration object.
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(const wpi::json& config);
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
std::string GetConfigJson() const;
/**
* Get a JSON configuration object.
*
* @return JSON configuration object
*/
wpi::json GetConfigJsonObject() const;
/**
* Get the actual FPS.
*
@@ -399,6 +449,11 @@ class UsbCamera : public VideoCamera {
*/
std::string GetPath() const;
/**
* Get the full camera information for the device.
*/
UsbCameraInfo GetInfo() const;
/**
* Set how verbose the camera connection messages are.
*
@@ -532,6 +587,15 @@ class AxisCamera : public HttpCamera {
*/
AxisCamera(const wpi::Twine& name, const std::string& host);
/**
* Create a source for an Axis IP camera.
*
* @param name Source name (arbitrary unique identifier)
* @param host Camera host IP or DNS name (e.g. "10.x.y.11")
* @param kind Camera kind (e.g. kAxis)
*/
AxisCamera(const wpi::Twine& name, wpi::StringRef host);
/**
* Create a source for an Axis IP camera.
*
@@ -743,6 +807,49 @@ class VideoSink {
*/
std::vector<VideoProperty> EnumerateProperties() const;
/**
* Set properties from a JSON configuration string.
*
* The format of the JSON input is:
*
* <pre>
* {
* "properties": [
* {
* "name": property name
* "value": property value
* }
* ]
* }
* </pre>
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(wpi::StringRef config);
/**
* Set properties from a JSON configuration object.
*
* @param config configuration
* @return True if set successfully
*/
bool SetConfigJson(const wpi::json& config);
/**
* Get a JSON configuration string.
*
* @return JSON configuration string
*/
std::string GetConfigJson() const;
/**
* Get a JSON configuration object.
*
* @return JSON configuration object
*/
wpi::json GetConfigJsonObject() const;
/**
* Configure which source should provide frames to this sink. Each sink
* can accept frames from only a single source, but a single source can

View File

@@ -170,6 +170,21 @@ inline bool VideoSource::SetFPS(int fps) {
return SetSourceFPS(m_handle, fps, &m_status);
}
inline bool VideoSource::SetConfigJson(wpi::StringRef config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
inline bool VideoSource::SetConfigJson(const wpi::json& config) {
m_status = 0;
return SetSourceConfigJson(m_handle, config, &m_status);
}
inline std::string VideoSource::GetConfigJson() const {
m_status = 0;
return GetSourceConfigJson(m_handle, &m_status);
}
inline double VideoSource::GetActualFPS() const {
m_status = 0;
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
@@ -245,6 +260,11 @@ inline std::string UsbCamera::GetPath() const {
return ::cs::GetUsbCameraPath(m_handle, &m_status);
}
inline UsbCameraInfo UsbCamera::GetInfo() const {
m_status = 0;
return ::cs::GetUsbCameraInfo(m_handle, &m_status);
}
inline void UsbCamera::SetConnectVerbose(int level) {
m_status = 0;
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
@@ -346,6 +366,9 @@ inline AxisCamera::AxisCamera(const wpi::Twine& name, const char* host)
inline AxisCamera::AxisCamera(const wpi::Twine& name, const std::string& host)
: HttpCamera(name, HostToUrl(wpi::Twine{host}), kAxis) {}
inline AxisCamera::AxisCamera(const wpi::Twine& name, wpi::StringRef host)
: HttpCamera(name, HostToUrl(host), kAxis) {}
inline AxisCamera::AxisCamera(const wpi::Twine& name,
wpi::ArrayRef<std::string> hosts)
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
@@ -498,6 +521,21 @@ inline VideoProperty VideoSink::GetSourceProperty(const wpi::Twine& name) {
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
}
inline bool VideoSink::SetConfigJson(wpi::StringRef config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline std::string VideoSink::GetConfigJson() const {
m_status = 0;
return GetSinkConfigJson(m_handle, &m_status);
}
inline MjpegServer::MjpegServer(const wpi::Twine& name,
const wpi::Twine& listenAddress, int port) {
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);

View File

@@ -24,6 +24,8 @@
#include <algorithm>
#include <wpi/FileSystem.h>
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/memory.h>
#include <wpi/raw_ostream.h>
@@ -1278,17 +1280,79 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return static_cast<UsbCameraImpl&>(*data->source).GetPath();
}
static const char* symlinkDirs[] = {"/dev/v4l/by-id", "/dev/v4l/by-path"};
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
UsbCameraInfo info;
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
*status = CS_INVALID_HANDLE;
return info;
}
std::string keypath = static_cast<UsbCameraImpl&>(*data->source).GetPath();
info.path = keypath;
// it might be a symlink; if so, find the symlink target (e.g. /dev/videoN),
// add that to the list and make it the keypath
if (wpi::sys::fs::is_symlink_file(keypath)) {
char* target = ::realpath(keypath.c_str(), nullptr);
if (target) {
keypath.assign(target);
info.otherPaths.emplace_back(keypath);
std::free(target);
}
}
// device number
wpi::StringRef fname = wpi::sys::path::filename(keypath);
if (fname.startswith("video")) fname.substr(5).getAsInteger(10, info.dev);
// description
info.name = GetDescriptionImpl(keypath.c_str());
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to the
// keypath
wpi::SmallString<128> path;
for (auto symlinkDir : symlinkDirs) {
if (DIR* dp = ::opendir(symlinkDir)) {
while (struct dirent* ep = ::readdir(dp)) {
if (ep->d_type == DT_LNK) {
path = symlinkDir;
path += '/';
path += ep->d_name;
char* target = ::realpath(path.c_str(), nullptr);
if (target) {
if (keypath == target) info.otherPaths.emplace_back(path.str());
std::free(target);
}
}
}
::closedir(dp);
}
}
// eliminate any duplicates
std::sort(info.otherPaths.begin(), info.otherPaths.end());
info.otherPaths.erase(
std::unique(info.otherPaths.begin(), info.otherPaths.end()),
info.otherPaths.end());
return info;
}
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
std::vector<UsbCameraInfo> retval;
if (DIR* dp = opendir("/dev")) {
while (struct dirent* ep = readdir(dp)) {
if (DIR* dp = ::opendir("/dev")) {
while (struct dirent* ep = ::readdir(dp)) {
wpi::StringRef fname{ep->d_name};
if (!fname.startswith("video")) continue;
unsigned int dev = 0;
if (fname.substr(5).getAsInteger(10, dev)) continue;
UsbCameraInfo info;
info.dev = -1;
fname.substr(5).getAsInteger(10, info.dev);
info.dev = dev;
wpi::SmallString<32> path{"/dev/"};
path += fname;
info.path = path.str();
@@ -1296,20 +1360,47 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
info.name = GetDescriptionImpl(path.c_str());
if (info.name.empty()) continue;
retval.emplace_back(std::move(info));
if (dev >= retval.size()) retval.resize(info.dev + 1);
retval[info.dev] = std::move(info);
}
closedir(dp);
::closedir(dp);
} else {
// *status = ;
WPI_ERROR(Instance::GetInstance().logger, "Could not open /dev");
return retval;
}
// sort by device number
std::sort(retval.begin(), retval.end(),
[](const UsbCameraInfo& a, const UsbCameraInfo& b) {
return a.dev < b.dev;
});
// look through /dev/v4l/by-id and /dev/v4l/by-path for symlinks to
// /dev/videoN
wpi::SmallString<128> path;
for (auto symlinkDir : symlinkDirs) {
if (DIR* dp = ::opendir(symlinkDir)) {
while (struct dirent* ep = ::readdir(dp)) {
if (ep->d_type == DT_LNK) {
path = symlinkDir;
path += '/';
path += ep->d_name;
char* target = ::realpath(path.c_str(), nullptr);
if (target) {
wpi::StringRef fname = wpi::sys::path::filename(target);
unsigned int dev = 0;
if (fname.startswith("video") &&
!fname.substr(5).getAsInteger(10, dev) && dev < retval.size()) {
retval[dev].otherPaths.emplace_back(path.str());
}
std::free(target);
}
}
}
::closedir(dp);
}
}
// remove devices with empty names
retval.erase(
std::remove_if(retval.begin(), retval.end(),
[](const UsbCameraInfo& x) { return x.name.empty(); }),
retval.end());
return retval;
}

View File

@@ -9,6 +9,7 @@
#include <wpi/STLExtras.h>
#include <wpi/SmallString.h>
#include <wpi/raw_ostream.h>
#include "UsbUtil.h"
@@ -151,6 +152,9 @@ UsbCameraProperty::UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl)
propKind = CS_PROP_BOOLEAN;
break;
case V4L2_CTRL_TYPE_INTEGER_MENU:
propKind = CS_PROP_ENUM;
intMenu = true;
break;
case V4L2_CTRL_TYPE_MENU:
propKind = CS_PROP_ENUM;
break;
@@ -243,7 +247,12 @@ std::unique_ptr<UsbCameraProperty> UsbCameraProperty::DeviceQuery(int fd,
for (int i = prop->minimum; i <= prop->maximum; ++i) {
qmenu.index = static_cast<__u32>(i);
if (TryIoctl(fd, VIDIOC_QUERYMENU, &qmenu) != 0) continue;
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
if (prop->intMenu) {
wpi::raw_string_ostream os(prop->enumChoices[i]);
os << qmenu.value;
} else {
prop->enumChoices[i] = reinterpret_cast<const char*>(qmenu.name);
}
}
}

View File

@@ -71,6 +71,9 @@ class UsbCameraProperty : public PropertyImpl {
unsigned id{0}; // implementation-level id
int type{0}; // implementation type, not CS_PropertyKind!
// If the enum property is integer rather than string
bool intMenu{false};
};
} // namespace cs

View File

@@ -26,6 +26,11 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return std::string{};
}
UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return UsbCameraInfo{};
}
std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
*status = CS_INVALID_HANDLE;
return std::vector<UsbCameraInfo>{};

View File

@@ -0,0 +1,158 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include <mfapi.h>
#include <mfidl.h>
#include <shlwapi.h>
#include <windowsx.h>
#include <Windows.h>
#include "UsbCameraImpl.h"
// https://github.com/opencv/opencv/blob/master/modules/videoio/src/cap_msmf.cpp
#include <mfidl.h>
#include <mfapi.h>
#include <Dbt.h>
#include <ks.h>
#include <ksmedia.h>
#include <mfreadwrite.h>
#include "COMCreators.h"
#include "ComPtr.h"
#pragma comment(lib, "Mfplat.lib")
#pragma comment(lib, "Mf.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "Ole32.lib")
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Mfreadwrite.lib")
#pragma comment(lib, "Shlwapi.lib")
namespace cs {
SourceReaderCB::SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
const cs::VideoMode& mode)
: m_nRefCount(1), m_source(source), m_mode{mode} {}
// IUnknown methods
STDMETHODIMP SourceReaderCB::QueryInterface(REFIID iid, void** ppv) {
static const QITAB qit[] = {
QITABENT(SourceReaderCB, IMFSourceReaderCallback),
{0},
};
return QISearch(this, qit, iid, ppv);
}
STDMETHODIMP_(ULONG) SourceReaderCB::AddRef() {
return InterlockedIncrement(&m_nRefCount);
}
STDMETHODIMP_(ULONG) SourceReaderCB::Release() {
ULONG uCount = InterlockedDecrement(&m_nRefCount);
if (uCount == 0) {
delete this;
}
return uCount;
}
STDMETHODIMP SourceReaderCB::OnEvent(DWORD, IMFMediaEvent*) { return S_OK; }
STDMETHODIMP SourceReaderCB::OnFlush(DWORD) { return S_OK; }
void SourceReaderCB::NotifyError(HRESULT hr) {
wprintf(L"Source Reader error: 0x%X\n", hr);
}
STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
DWORD dwStreamFlags,
LONGLONG llTimestamp,
IMFSample* pSample // Can be NULL
) {
auto source = m_source.lock();
if (!source) return S_OK;
if (SUCCEEDED(hrStatus)) {
if (pSample) {
// Prcoess sample
source->ProcessFrame(pSample, m_mode);
// DO NOT release the frame
}
} else {
// Streaming error.
NotifyError(hrStatus);
}
// Trigger asking for a new frame.
// This is piped through the message pump for concurrency reasons
source->PostRequestNewFrame();
return S_OK;
}
// Create a Source Reader COM Smart Object
ComPtr<SourceReaderCB> CreateSourceReaderCB(
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode) {
SourceReaderCB* ptr = new SourceReaderCB(source, mode);
ComPtr<SourceReaderCB> sourceReaderCB;
sourceReaderCB.Attach(ptr);
return sourceReaderCB;
}
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink) {
ComPtr<IMFAttributes> pAttributes;
ComPtr<IMFMediaSource> pSource;
HRESULT hr = MFCreateAttributes(pAttributes.GetAddressOf(), 2);
// Set the device type to video.
if (SUCCEEDED(hr)) {
hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
}
// Set the symbolic link.
if (SUCCEEDED(hr)) {
hr = pAttributes->SetString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
pszSymbolicLink);
}
if (SUCCEEDED(hr)) {
hr = MFCreateDeviceSource(pAttributes.Get(), pSource.GetAddressOf());
}
// No need to check last HR, as the source would be null anyway.
return pSource;
}
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
IMFSourceReaderCallback* callback) {
HRESULT hr = S_OK;
ComPtr<IMFAttributes> pAttributes;
ComPtr<IMFSourceReader> sourceReader;
hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1);
if (FAILED(hr)) {
return nullptr;
}
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback);
if (FAILED(hr)) {
return nullptr;
}
hr = pAttributes->SetUINT32(
MF_SOURCE_READER_DISCONNECT_MEDIASOURCE_ON_SHUTDOWN, TRUE);
if (FAILED(hr)) {
return nullptr;
}
MFCreateSourceReaderFromMediaSource(mediaSource, pAttributes.Get(),
sourceReader.GetAddressOf());
// No need to check last HR, as the sourceReader would be null anyway.
return sourceReader;
}
} // namespace cs

View File

@@ -0,0 +1,58 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <mfidl.h>
#include <mfreadwrite.h>
#include <memory>
#include "ComPtr.h"
#include "cscore_cpp.h"
namespace cs {
class UsbCameraImpl;
// Source callback used by the source reader.
// COM object, so it needs a to ref count itself.
class SourceReaderCB : public IMFSourceReaderCallback {
public:
explicit SourceReaderCB(std::weak_ptr<cs::UsbCameraImpl> source,
const cs::VideoMode& mode);
void SetVideoMode(const VideoMode& mode) { m_mode = mode; }
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP OnEvent(DWORD, IMFMediaEvent*);
STDMETHODIMP OnFlush(DWORD);
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
DWORD dwStreamFlags, LONGLONG llTimestamp,
IMFSample* pSample // Can be NULL
);
void InvalidateCapture() { m_source = std::weak_ptr<cs::UsbCameraImpl>(); }
private:
// Destructor is private. Caller should call Release.
virtual ~SourceReaderCB() {}
void NotifyError(HRESULT hr);
ULONG m_nRefCount;
std::weak_ptr<cs::UsbCameraImpl> m_source;
cs::VideoMode m_mode;
};
ComPtr<SourceReaderCB> CreateSourceReaderCB(
std::weak_ptr<cs::UsbCameraImpl> source, const cs::VideoMode& mode);
ComPtr<IMFSourceReader> CreateSourceReader(IMFMediaSource* mediaSource,
IMFSourceReaderCallback* callback);
ComPtr<IMFMediaSource> CreateVideoCaptureDevice(LPCWSTR pszSymbolicLink);
} // namespace cs

View File

@@ -0,0 +1,152 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <comdef.h>
#include <shlwapi.h> // QISearch
#include <cassert>
namespace cs {
template <typename Interface>
class RemoveAddRefRelease : public Interface {
ULONG __stdcall AddRef();
ULONG __stdcall Release();
virtual ~RemoveAddRefRelease();
};
template <typename Interface>
class ComPtr {
public:
template <typename T>
friend class ComPtr;
ComPtr(std::nullptr_t = nullptr) noexcept {} // NOLINT(runtime/explicit)
ComPtr(const ComPtr& other) noexcept : m_ptr(other.m_ptr) {
InternalAddRef();
}
template <typename T>
ComPtr(const ComPtr<T>& other) noexcept : m_ptr(other.m_ptr) {
InternalAddRef();
}
template <typename T>
ComPtr(ComPtr<T>&& other) noexcept : m_ptr(other.m_ptr) {
other.m_ptr = nullptr;
}
~ComPtr() noexcept { InternalRelease(); }
ComPtr& operator=(const ComPtr& other) noexcept {
InternalCopy(other.m_ptr);
return *this;
}
template <typename T>
ComPtr& operator=(const ComPtr<T>& other) noexcept {
InternalCopy(other.m_ptr);
return *this;
}
template <typename T>
ComPtr& operator=(ComPtr<T>&& other) noexcept {
InternalMove(other);
return *this;
}
void Swap(ComPtr& other) noexcept {
Interface* temp = m_ptr;
m_ptr = other.m_ptr;
other.m_ptr = temp;
}
explicit operator bool() const noexcept { return nullptr != m_ptr; }
void Reset() noexcept { InternalRelease(); }
Interface* Get() const noexcept { return m_ptr; }
Interface* Detach() noexcept {
Interface* temp = m_ptr;
m_ptr = nullptr;
return temp;
}
void Copy(Interface* other) noexcept { InternalCopy(other); }
void Attach(Interface* other) noexcept {
InternalRelease();
m_ptr = other;
}
Interface** GetAddressOf() noexcept {
assert(m_ptr == nullptr);
return &m_ptr;
}
void CopyTo(Interface** other) const noexcept {
InternalAddRef();
*other = m_ptr;
}
template <typename T>
ComPtr<T> As() const noexcept {
ComPtr<T> temp;
m_ptr->QueryInterface(temp.GetAddressOf());
return temp;
}
RemoveAddRefRelease<Interface>* operator->() const noexcept {
return static_cast<RemoveAddRefRelease<Interface>*>(m_ptr);
}
private:
Interface* m_ptr = nullptr;
void InternalAddRef() const noexcept {
if (m_ptr) {
m_ptr->AddRef();
}
}
void InternalRelease() noexcept {
Interface* temp = m_ptr;
if (temp) {
m_ptr = nullptr;
temp->Release();
}
}
void InternalCopy(Interface* other) noexcept {
if (m_ptr != other) {
InternalRelease();
m_ptr = other;
InternalAddRef();
}
}
template <typename T>
void InternalMove(ComPtr<T>& other) noexcept {
if (m_ptr != other.m_ptr) {
InternalRelease();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
}
};
template <typename Interface>
void swap(
ComPtr<Interface>& left,
ComPtr<Interface>& right) noexcept { // NOLINT(build/include_what_you_use)
left.Swap(right);
}
} // namespace cs

View File

@@ -7,14 +7,55 @@
#include "NetworkListener.h"
#include <winsock2.h> // NOLINT(build/include_order)
#include <windows.h> // NOLINT(build/include_order)
#include <ws2def.h> // NOLINT(build/include_order)
#include <ws2ipdef.h> // NOLINT(build/include_order)
#include <iphlpapi.h> // NOLINT(build/include_order)
#include <netioapi.h> // NOLINT(build/include_order)
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#pragma comment(lib, "Iphlpapi.lib")
using namespace cs;
class NetworkListener::Impl {};
class NetworkListener::Impl {
public:
Impl(wpi::Logger& logger, Notifier& notifier)
: m_logger(logger), m_notifier(notifier) {}
wpi::Logger& m_logger;
Notifier& m_notifier;
HANDLE eventHandle = 0;
};
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) {}
// Static Callback function for NotifyIpInterfaceChange API.
static void WINAPI OnInterfaceChange(PVOID callerContext,
PMIB_IPINTERFACE_ROW row,
MIB_NOTIFICATION_TYPE notificationType) {
Notifier* notifier = reinterpret_cast<Notifier*>(callerContext);
notifier->NotifyNetworkInterfacesChanged();
}
NetworkListener::~NetworkListener() {}
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
void NetworkListener::Start() {}
NetworkListener::~NetworkListener() { Stop(); }
void NetworkListener::Stop() {}
void NetworkListener::Start() {
NotifyIpInterfaceChange(AF_INET, OnInterfaceChange, &m_impl->m_notifier, true,
&m_impl->eventHandle);
}
void NetworkListener::Stop() {
if (m_impl->eventHandle) {
CancelMibChangeNotify2(m_impl->eventHandle);
}
}

View File

@@ -5,12 +5,38 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include <uv.h>
#include "cscore_cpp.h"
#pragma comment(lib, "Ws2_32.lib")
namespace cs {
std::vector<std::string> GetNetworkInterfaces() {
return std::vector<std::string>{}; // TODO
uv_interface_address_t* adrs;
int counts = 0;
std::vector<std::string> addresses{};
uv_interface_addresses(&adrs, &counts);
char ip[50];
for (int i = 0; i < counts; i++) {
if (adrs[i].is_internal) continue;
InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip,
sizeof(ip) - 1);
ip[49] = '\0';
InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip,
sizeof(ip) - 1);
ip[49] = '\0';
addresses.emplace_back(std::string{ip});
}
uv_free_interface_addresses(adrs, counts);
return addresses;
}
} // namespace cs

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#ifndef CSCORE_USBCAMERAIMPL_H_
#define CSCORE_USBCAMERAIMPL_H_
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <ks.h> // NOLINT(build/include_order)
#include <ksmedia.h> // NOLINT(build/include_order)
#include <atomic>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <Dbt.h>
#include <wpi/STLExtras.h>
#include <wpi/SmallVector.h>
#include <wpi/Twine.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include "ComCreators.h"
#include "ComPtr.h"
#include "SourceImpl.h"
#include "UsbCameraProperty.h"
#include "WindowsMessagePump.h"
namespace cs {
class UsbCameraImpl : public SourceImpl,
public std::enable_shared_from_this<UsbCameraImpl> {
public:
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, const wpi::Twine& path);
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, int deviceId);
~UsbCameraImpl() override;
void Start();
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, const wpi::Twine& value,
CS_Status* status) override;
// Standard common camera properties
void SetBrightness(int brightness, CS_Status* status) override;
int GetBrightness(CS_Status* status) const override;
void SetWhiteBalanceAuto(CS_Status* status) override;
void SetWhiteBalanceHoldCurrent(CS_Status* status) override;
void SetWhiteBalanceManual(int value, CS_Status* status) override;
void SetExposureAuto(CS_Status* status) override;
void SetExposureHoldCurrent(CS_Status* status) override;
void SetExposureManual(int value, CS_Status* status) override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;
bool SetPixelFormat(VideoMode::PixelFormat pixelFormat,
CS_Status* status) override;
bool SetResolution(int width, int height, CS_Status* status) override;
bool SetFPS(int fps, CS_Status* status) override;
void NumSinksChanged() override;
void NumSinksEnabledChanged() override;
void ProcessFrame(IMFSample* sample, const VideoMode& mode);
void PostRequestNewFrame();
std::string GetPath() { return m_path; }
// Messages passed to/from camera thread
struct Message {
enum Kind {
kNone = 0,
kCmdSetMode,
kCmdSetPixelFormat,
kCmdSetResolution,
kCmdSetFPS,
kCmdSetProperty,
kCmdSetPropertyStr,
kNumSinksChanged, // no response
kNumSinksEnabledChanged, // no response
// Responses
kOk,
kError
};
explicit Message(Kind kind_)
: kind(kind_), from(std::this_thread::get_id()) {}
Kind kind;
int data[4];
std::string dataStr;
std::thread::id from;
};
protected:
std::unique_ptr<PropertyImpl> CreateEmptyProperty(
const wpi::Twine& name) const override;
// Cache properties. Immediately successful if properties are already cached.
// If they are not, tries to connect to the camera to do so; returns false and
// sets status to CS_SOURCE_IS_DISCONNECTED if that too fails.
bool CacheProperties(CS_Status* status) const override;
private:
// The camera processing thread
void CameraThreadMain();
LRESULT PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
bool CheckDeviceChange(WPARAM wParam, DEV_BROADCAST_HDR* pHdr,
bool* connected);
// Functions used by CameraThreadMain()
void DeviceDisconnect();
bool DeviceConnect();
bool DeviceStreamOn();
bool DeviceStreamOff();
CS_StatusValue DeviceSetMode();
void DeviceCacheMode();
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp,
IMFSourceReader* sourceReader);
void DeviceCacheProperties();
void DeviceCacheVideoModes();
template <typename TagProperty, typename IAM>
void DeviceAddProperty(const wpi::Twine& name_, TagProperty tag,
IAM* pProcAmp);
ComPtr<IMFMediaType> DeviceCheckModeValid(const VideoMode& toCheck);
// Command helper functions
CS_StatusValue DeviceProcessCommand(std::unique_lock<wpi::mutex>& lock,
Message::Kind msgKind,
const Message* msg);
CS_StatusValue DeviceCmdSetMode(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
CS_StatusValue DeviceCmdSetProperty(std::unique_lock<wpi::mutex>& lock,
const Message& msg);
// Property helper functions
int RawToPercentage(const UsbCameraProperty& rawProp, int rawValue);
int PercentageToRaw(const UsbCameraProperty& rawProp, int percentValue);
//
// Variables only used within camera thread
//
bool m_streaming{false};
bool m_wasStreaming{false};
bool m_modeSet{false};
int m_connectVerbose{1};
bool m_deviceValid{false};
ComPtr<IMFMediaSource> m_mediaSource;
ComPtr<IMFSourceReader> m_sourceReader;
ComPtr<SourceReaderCB> m_imageCallback;
std::unique_ptr<cs::WindowsMessagePump> m_messagePump;
ComPtr<IMFMediaType> m_currentMode;
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
std::wstring m_widePath;
int m_deviceId;
std::vector<std::pair<VideoMode, ComPtr<IMFMediaType>>> m_windowsVideoModes;
};
} // namespace cs
#endif // CSCORE_USBCAMERAIMPL_H_

View File

@@ -0,0 +1,189 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "UsbCameraProperty.h"
#include "ComPtr.h"
using namespace cs;
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
tagVideoProcAmpProperty tag, bool autoProp,
IAMVideoProcAmp* pProcAmp, bool* isValid)
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
this->tagVideoProc = tag;
this->isControlProperty = false;
this->isAutoProp = autoProp;
long paramVal, paramFlag; // NOLINT(runtime/int)
HRESULT hr;
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, &paramVal,
&paramFlag); // Unable to get the property, trying to
// return default value
if (SUCCEEDED(hr)) {
minimum = minVal;
maximum = maxVal;
hasMaximum = true;
hasMinimum = true;
defaultValue = paramVal;
step = stepVal;
value = paramVal;
propKind = CS_PropertyKind::CS_PROP_INTEGER;
*isValid = true;
} else {
*isValid = false;
}
}
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) {
if (!pProcAmp) return true;
lock.unlock();
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
if (SUCCEEDED(pProcAmp->Get(tagVideoProc, &newValue, &paramFlag))) {
lock.lock();
value = newValue;
return true;
}
return false;
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) const {
return DeviceSet(lock, pProcAmp, value);
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp,
int newValue) const {
if (!pProcAmp) return true;
lock.unlock();
if (SUCCEEDED(
pProcAmp->Set(tagVideoProc, newValue, VideoProcAmp_Flags_Manual))) {
lock.lock();
return true;
}
return false;
}
UsbCameraProperty::UsbCameraProperty(const wpi::Twine& name_,
tagCameraControlProperty tag,
bool autoProp, IAMCameraControl* pProcAmp,
bool* isValid)
: PropertyImpl{autoProp ? name_ + "_auto" : name_} {
this->tagCameraControl = tag;
this->isControlProperty = true;
this->isAutoProp = autoProp;
long paramVal, paramFlag; // NOLINT(runtime/int)
HRESULT hr;
long minVal, maxVal, stepVal; // NOLINT(runtime/int)
hr = pProcAmp->GetRange(tag, &minVal, &maxVal, &stepVal, &paramVal,
&paramFlag); // Unable to get the property, trying to
// return default value
if (SUCCEEDED(hr)) {
minimum = minVal;
maximum = maxVal;
hasMaximum = true;
hasMinimum = true;
defaultValue = paramVal;
step = stepVal;
value = paramVal;
propKind = CS_PropertyKind::CS_PROP_INTEGER;
*isValid = true;
} else {
*isValid = false;
}
}
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
IAMCameraControl* pProcAmp) {
if (!pProcAmp) return true;
lock.unlock();
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
if (SUCCEEDED(pProcAmp->Get(tagCameraControl, &newValue, &paramFlag))) {
lock.lock();
value = newValue;
return true;
}
return false;
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMCameraControl* pProcAmp) const {
return DeviceSet(lock, pProcAmp, value);
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMCameraControl* pProcAmp,
int newValue) const {
if (!pProcAmp) return true;
lock.unlock();
if (SUCCEEDED(pProcAmp->Set(tagCameraControl, newValue,
CameraControl_Flags_Manual))) {
lock.lock();
return true;
}
return false;
}
bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader) {
if (!sourceReader) return true;
if (isControlProperty) {
ComPtr<IAMCameraControl> pProcAmp;
if (SUCCEEDED(sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
return DeviceGet(lock, pProcAmp.Get());
} else {
return false;
}
} else {
ComPtr<IAMVideoProcAmp> pProcAmp;
if (SUCCEEDED(sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
return DeviceGet(lock, pProcAmp.Get());
} else {
return false;
}
}
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader) const {
return DeviceSet(lock, sourceReader, value);
}
bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader,
int newValue) const {
if (!sourceReader) return true;
if (isControlProperty) {
ComPtr<IAMCameraControl> pProcAmp;
if (SUCCEEDED(sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
return DeviceSet(lock, pProcAmp.Get(), newValue);
} else {
return false;
}
} else {
ComPtr<IAMVideoProcAmp> pProcAmp;
if (SUCCEEDED(sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(pProcAmp.GetAddressOf())))) {
return DeviceSet(lock, pProcAmp.Get(), newValue);
} else {
return false;
}
}
}

View File

@@ -0,0 +1,97 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <memory>
#include <Dshow.h>
#include <wpi/mutex.h>
#include "PropertyImpl.h"
namespace cs {
// Property data
class UsbCameraProperty : public PropertyImpl {
public:
UsbCameraProperty() = default;
explicit UsbCameraProperty(const wpi::Twine& name_) : PropertyImpl{name_} {}
// Software property constructor
UsbCameraProperty(const wpi::Twine& name_, unsigned id_,
CS_PropertyKind kind_, int minimum_, int maximum_,
int step_, int defaultValue_, int value_)
: PropertyImpl(name_, kind_, minimum_, maximum_, step_, defaultValue_,
value_),
device{false},
id{id_} {}
// Normalized property constructor
UsbCameraProperty(const wpi::Twine& name_, int rawIndex_,
const UsbCameraProperty& rawProp, int defaultValue_,
int value_)
: PropertyImpl(name_, rawProp.propKind, 1, defaultValue_, value_),
percentage{true},
propPair{rawIndex_},
id{rawProp.id},
type{rawProp.type} {
hasMinimum = true;
minimum = 0;
hasMaximum = true;
maximum = 100;
}
UsbCameraProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
bool autoProp, IAMVideoProcAmp* pProcAmp, bool* isValid);
UsbCameraProperty(const wpi::Twine& name_, tagCameraControlProperty tag,
bool autoProp, IAMCameraControl* pProcAmp, bool* isValid);
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader);
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
IMFSourceReader* sourceReader, int newValue) const;
// If this is a device (rather than software) property
bool device{true};
bool isAutoProp{true};
bool isControlProperty{false};
tagVideoProcAmpProperty tagVideoProc;
tagCameraControlProperty tagCameraControl;
// If this is a percentage (rather than raw) property
bool percentage{false};
// If not 0, index of corresponding raw/percentage property
int propPair{0};
unsigned id{0}; // implementation-level id
int type{0}; // implementation type, not CS_PropertyKind!
private:
bool DeviceGet(std::unique_lock<wpi::mutex>& lock,
IAMCameraControl* pProcAmp);
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMCameraControl* pProcAmp) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMCameraControl* pProcAmp,
int newValue) const;
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp);
bool DeviceSet(std::unique_lock<wpi::mutex>& lock,
IAMVideoProcAmp* pProcAmp) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp,
int newValue) const;
};
} // namespace cs

View File

@@ -0,0 +1,152 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "WindowsMessagePump.h"
#include <ks.h>
#include <ksmedia.h>
#include <mfapi.h>
#include <mfidl.h>
#include <windows.h>
#include <windowsx.h>
#include <memory>
#include <Dbt.h>
#pragma comment(lib, "Mfplat.lib")
#pragma comment(lib, "Mf.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "Ole32.lib")
#pragma comment(lib, "User32.lib")
namespace cs {
static LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
LPARAM lParam) {
WindowsMessagePump* pumpContainer;
// Our "this" parameter is passed only during WM_CREATE
// If it is create, store in our user parameter
// Otherwise grab from our user parameter
if (uiMsg == WM_CREATE) {
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pumpContainer =
reinterpret_cast<WindowsMessagePump*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pumpContainer);
SetWindowPos(hwnd, HWND_MESSAGE, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
} else {
pumpContainer = reinterpret_cast<WindowsMessagePump*>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));
}
// Run the callback
bool hasCalledBack = false;
LRESULT result;
if (pumpContainer) {
hasCalledBack = true;
result = pumpContainer->m_callback(hwnd, uiMsg, wParam, lParam);
}
// Handle a close message
if (uiMsg == WM_CLOSE) {
return HANDLE_WM_CLOSE(hwnd, 0, 0, [](HWND) { PostQuitMessage(0); });
}
// Return message, otherwise return the base handler
if (hasCalledBack) {
return result;
}
return DefWindowProc(hwnd, uiMsg, wParam, lParam);
}
namespace {
struct ClassHolder {
HINSTANCE current_instance;
WNDCLASSEX wx;
const char* class_name = "DUMMY_CLASS";
ClassHolder() {
current_instance = (HINSTANCE)GetModuleHandle(NULL);
wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = pWndProc; // function which will handle messages
wx.hInstance = current_instance;
wx.lpszClassName = class_name;
RegisterClassEx(&wx);
}
~ClassHolder() { UnregisterClass(class_name, current_instance); }
};
} // namespace
static std::shared_ptr<ClassHolder> GetClassHolder() {
static std::shared_ptr<ClassHolder> clsHolder =
std::make_shared<ClassHolder>();
return clsHolder;
}
WindowsMessagePump::WindowsMessagePump(
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback) {
m_callback = callback;
auto handle = CreateEvent(NULL, true, false, NULL);
m_mainThread = std::thread([=]() { ThreadMain(handle); });
auto waitResult = WaitForSingleObject(handle, 1000);
if (waitResult == WAIT_OBJECT_0) {
CloseHandle(handle);
}
}
WindowsMessagePump::~WindowsMessagePump() {
auto res = SendMessage(hwnd, WM_CLOSE, NULL, NULL);
if (m_mainThread.joinable()) m_mainThread.join();
}
void WindowsMessagePump::ThreadMain(HANDLE eventHandle) {
// Initialize COM
CoInitializeEx(0, COINIT_MULTITHREADED);
// Initialize MF
MFStartup(MF_VERSION);
auto classHolder = GetClassHolder();
hwnd = CreateWindowEx(0, classHolder->class_name, "dummy_name", 0, 0, 0, 0, 0,
HWND_MESSAGE, NULL, NULL, this);
// Register for device notifications
HDEVNOTIFY g_hdevnotify = NULL;
HDEVNOTIFY g_hdevnotify2 = NULL;
DEV_BROADCAST_DEVICEINTERFACE di = {0};
di.dbcc_size = sizeof(di);
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
di.dbcc_classguid = KSCATEGORY_CAPTURE;
g_hdevnotify =
RegisterDeviceNotification(hwnd, &di, DEVICE_NOTIFY_WINDOW_HANDLE);
DEV_BROADCAST_DEVICEINTERFACE di2 = {0};
di2.dbcc_size = sizeof(di2);
di2.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
di2.dbcc_classguid = KSCATEGORY_VIDEO_CAMERA;
g_hdevnotify2 =
RegisterDeviceNotification(hwnd, &di2, DEVICE_NOTIFY_WINDOW_HANDLE);
SetEvent(eventHandle);
MSG Msg;
while (GetMessage(&Msg, NULL, 0, 0) > 0) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
UnregisterDeviceNotification(g_hdevnotify);
UnregisterDeviceNotification(g_hdevnotify2);
MFShutdown();
CoUninitialize();
}
} // namespace cs

View File

@@ -0,0 +1,66 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <windows.h>
#include <functional>
#include <thread>
namespace cs {
class WindowsMessagePump {
public:
WindowsMessagePump(
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> callback);
~WindowsMessagePump();
friend LRESULT CALLBACK pWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
LPARAM lParam);
template <typename RetVal = LRESULT, typename FirstParam = WPARAM,
typename SecondParam = LPARAM>
RetVal SendWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
"First Parameter Does Not Fit");
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
"Second Parameter Does Not Fit");
static_assert(sizeof(RetVal) <= sizeof(LRESULT),
"Return Value Does Not Fit");
WPARAM firstToSend = 0;
LPARAM secondToSend = 0;
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
LRESULT result = SendMessage(hwnd, msg, firstToSend, secondToSend);
RetVal toReturn;
std::memset(&toReturn, 0, sizeof(RetVal));
std::memcpy(&toReturn, &result, sizeof(RetVal));
return toReturn;
}
template <typename FirstParam = WPARAM, typename SecondParam = LPARAM>
BOOL PostWindowMessage(UINT msg, FirstParam wParam, SecondParam lParam) {
static_assert(sizeof(FirstParam) <= sizeof(WPARAM),
"First Parameter Does Not Fit");
static_assert(sizeof(SecondParam) <= sizeof(LPARAM),
"Second Parameter Does Not Fit");
WPARAM firstToSend = 0;
LPARAM secondToSend = 0;
std::memcpy(&firstToSend, &wParam, sizeof(FirstParam));
std::memcpy(&secondToSend, &lParam, sizeof(SecondParam));
return PostMessage(hwnd, msg, firstToSend, secondToSend);
}
private:
void ThreadMain(HANDLE eventHandle);
HWND hwnd;
std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> m_callback;
std::thread m_mainThread;
};
} // namespace cs

View File

@@ -1,6 +1,6 @@
plugins {
id 'org.ysb33r.doxygen' version '0.4'
id 'java'
id "org.ysb33r.doxygen" version "0.5"
}
evaluationDependsOn(':wpiutil')
@@ -112,6 +112,19 @@ task generateJavaDocs(type: Javadoc) {
source configurations.javaSource.collect { zipTree(it) }
include '**/*.java'
failOnError = true
title = "WPILib API $pubVersion"
ext.entryPoint = "$destinationDir/index.html"
if (JavaVersion.current().isJava11Compatible()) {
options.addBooleanOption('-no-module-directories', true)
doLast {
// This is a work-around for https://bugs.openjdk.java.net/browse/JDK-8211194. Can be removed once that issue is fixed on JDK's side
// Since JDK 11, package-list is missing from javadoc output files and superseded by element-list file, but a lot of external tools still need it
// Here we generate this file manually
new File(destinationDir, 'package-list').text = new File(destinationDir, 'element-list').text
}
}
}
tasks.register("zipJavaDocs", Zip) {

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -28,7 +28,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

2
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

View File

@@ -26,7 +26,7 @@ file(GLOB
hal_shared_native_src src/main/native/cpp/cpp/*.cpp
hal_shared_native_src src/main/native/cpp/handles/*.cpp
hal_sim_native_src src/main/native/sim/*.cpp
hal_sim_native_src src/main/native/sim/MockData/*.cpp)
hal_sim_native_src src/main/native/sim/mockdata/*.cpp)
add_library(hal ${hal_shared_native_src})
set_target_properties(hal PROPERTIES DEBUG_POSTFIX "d")

View File

@@ -179,5 +179,25 @@ model {
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure']
}
halJNI(ExportsConfig) {
x86SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
retList << symbol
}
}
return retList
}
x64SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
retList << symbol
}
}
return retList
}
}
}
}

View File

@@ -80,3 +80,6 @@ kResourceType_Shuffleboard = 78
kResourceType_CAN = 79
kResourceType_DigilentDMC60 = 80
kResourceType_PWMVictorSPX = 81
kResourceType_RevSparkMaxPWM = 82
kResourceType_RevSparkMaxCAN = 83
kResourceType_ADIS16470 = 84

View File

@@ -25,9 +25,9 @@ public class InterruptJNI extends JNIWrapper {
public static native void disableInterrupts(int interruptHandle);
public static native double readInterruptRisingTimestamp(int interruptHandle);
public static native long readInterruptRisingTimestamp(int interruptHandle);
public static native double readInterruptFallingTimestamp(int interruptHandle);
public static native long readInterruptFallingTimestamp(int interruptHandle);
public static native void requestInterrupts(int interruptHandle, int digitalSourceHandle,
int analogTriggerType);

View File

@@ -57,7 +57,7 @@ public class SPIJNI extends JNIWrapper {
public static native int spiReadAutoReceivedData(int port, ByteBuffer buffer, int numToRead,
double timeout);
public static native int spiReadAutoReceivedData(int port, byte[] buffer, int numToRead,
public static native int spiReadAutoReceivedData(int port, int[] buffer, int numToRead,
double timeout);
public static native int spiGetAutoDroppedCount(int port);

View File

@@ -75,6 +75,9 @@ public class DriverStationSim {
public void setDsAttached(boolean dsAttached) {
DriverStationDataJNI.setDsAttached(dsAttached);
}
public void notifyNewData() {
DriverStationDataJNI.notifyNewData();
}
public void resetData() {
DriverStationDataJNI.resetData();

View File

@@ -8,5 +8,5 @@
package edu.wpi.first.hal.sim;
public interface SpiReadAutoReceiveBufferCallback {
int callback(String name, byte[] buffer, int numToRead);
int callback(String name, int[] buffer, int numToRead);
}

View File

@@ -22,7 +22,7 @@ using namespace hal;
namespace {
struct Receives {
uint64_t lastTimeStamp;
uint32_t lastTimeStamp;
uint8_t data[8];
uint8_t length;
};
@@ -40,30 +40,13 @@ struct CANStorage {
static UnlimitedHandleResource<HAL_CANHandle, CANStorage, HAL_HandleEnum::CAN>*
canHandles;
static std::atomic_bool HasFixedTime{false};
static uint64_t timeSpanDiff;
static void CheckDeltaTime() {
if (HasFixedTime) return;
HasFixedTime = true;
// TODO: Fix locking
static uint32_t GetPacketBaseTime() {
timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
int32_t status = 0;
uint64_t fpgaTime = HAL_GetFPGATime(&status);
// Convert t to microseconds
uint64_t us = t.tv_sec * 1000000 + t.tv_nsec / 1000;
timeSpanDiff =
us - fpgaTime; // This assumes CLOCK_MONOTONIC is greater then FPGA Time.
}
static inline uint64_t ConvertToFPGATime(uint32_t canMs) {
uint64_t canMsToUs = canMs * 1000;
return canMsToUs - timeSpanDiff;
// Convert t to milliseconds
uint64_t ms = t.tv_sec * 1000ull + t.tv_nsec / 1000000ull;
return ms & 0xFFFFFFFF;
}
namespace hal {
@@ -89,7 +72,6 @@ HAL_CANHandle HAL_InitializeCAN(HAL_CANManufacturer manufacturer,
int32_t deviceId, HAL_CANDeviceType deviceType,
int32_t* status) {
hal::init::CheckInit();
CheckDeltaTime();
auto can = std::make_shared<CANStorage>();
auto handle = canHandles->Allocate(can);
@@ -189,17 +171,16 @@ void HAL_ReadCANPacketNew(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
if (*status == 0) {
std::lock_guard<wpi::mutex> lock(can->mapMutex);
auto& msg = can->receives[messageId];
msg.length = dataSize;
msg.lastTimeStamp = timestamp;
msg.lastTimeStamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
}
*length = dataSize;
*receivedTimestamp = timestamp;
*receivedTimestamp = ts;
}
void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
@@ -216,21 +197,21 @@ void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;
@@ -253,29 +234,28 @@ void HAL_ReadCANPacketTimeout(HAL_CANHandle handle, int32_t apiId,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
// Found, check if new enough
uint64_t now = HAL_GetFPGATime(status);
if (now - i->second.lastTimeStamp >
static_cast<uint64_t>(timeoutMs) * 1000) {
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
// Timeout, return bad status
*status = HAL_CAN_TIMEOUT;
return;
}
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;
@@ -299,14 +279,15 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
std::lock_guard<wpi::mutex> lock(can->mapMutex);
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
uint64_t now = HAL_GetFPGATime(status);
// Found, check if new enough
if (now - i->second.lastTimeStamp <
static_cast<uint64_t>(periodMs) * 1000) {
*status = 0;
std::memcpy(i->second.data, data, i->second.length);
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;
return;
}
}
}
@@ -315,29 +296,28 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
// Found, check if new enough
uint64_t now = HAL_GetFPGATime(status);
if (now - i->second.lastTimeStamp >
static_cast<uint64_t>(timeoutMs) * 1000) {
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
// Timeout, return bad status
*status = HAL_CAN_TIMEOUT;
return;
}
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;

View File

@@ -207,6 +207,10 @@ const char* HAL_GetErrorMessage(int32_t code) {
return HAL_SERIAL_PORT_ERROR_MESSAGE;
case HAL_CAN_TIMEOUT:
return HAL_CAN_TIMEOUT_MESSAGE;
case ERR_FRCSystem_NetCommNotResponding:
return ERR_FRCSystem_NetCommNotResponding_MESSAGE;
case ERR_FRCSystem_NoDSConnection:
return ERR_FRCSystem_NoDSConnection_MESSAGE;
default:
return "Unknown error status";
}
@@ -235,7 +239,7 @@ uint64_t HAL_GetFPGATime(int32_t* status) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
}
*status = 0;
uint64_t upper1 = global->readLocalTimeUpper(status);
uint32_t lower = global->readLocalTime(status);
uint64_t upper2 = global->readLocalTimeUpper(status);

View File

@@ -165,26 +165,26 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
anInterrupt->manager->disable(status);
}
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return 0;
}
uint32_t timestamp = anInterrupt->anInterrupt->readRisingTimeStamp(status);
return timestamp * 1e-6;
}
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return 0;
}
uint32_t timestamp = anInterrupt->anInterrupt->readRisingTimeStamp(status);
return timestamp;
}
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto anInterrupt = interruptHandles->Get(interruptHandle);
if (anInterrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
return 0;
}
uint32_t timestamp = anInterrupt->anInterrupt->readFallingTimeStamp(status);
return timestamp * 1e-6;
return timestamp;
}
void HAL_RequestInterrupts(HAL_InterruptHandle interruptHandle,

View File

@@ -9,6 +9,8 @@
#include <memory>
#include <wpi/mutex.h>
#include "HALInitializer.h"
#include "PortsInternal.h"
#include "hal/CANAPI.h"
@@ -31,7 +33,7 @@ static constexpr int32_t StatusEnergy = 0x5D;
static constexpr int32_t Control1 = 0x70;
static constexpr int32_t TimeoutMs = 50;
static constexpr int32_t TimeoutMs = 100;
static constexpr int32_t StatusPeriodMs = 25;
/* encoder/decoders */
@@ -106,9 +108,16 @@ union PdpStatusEnergy {
} bits;
};
static wpi::mutex pdpHandleMutex;
static HAL_PDPHandle pdpHandles[kNumPDPModules];
namespace hal {
namespace init {
void InitializePDP() {}
void InitializePDP() {
for (int i = 0; i < kNumPDPModules; i++) {
pdpHandles[i] = HAL_kInvalidHandle;
}
}
} // namespace init
} // namespace hal
@@ -121,6 +130,13 @@ HAL_PDPHandle HAL_InitializePDP(int32_t module, int32_t* status) {
return HAL_kInvalidHandle;
}
std::lock_guard<wpi::mutex> lock(pdpHandleMutex);
if (pdpHandles[module] != HAL_kInvalidHandle) {
*status = 0;
return pdpHandles[module];
}
auto handle = HAL_InitializeCAN(manufacturer, module, deviceType, status);
if (*status != 0) {
@@ -128,10 +144,21 @@ HAL_PDPHandle HAL_InitializePDP(int32_t module, int32_t* status) {
return HAL_kInvalidHandle;
}
pdpHandles[module] = handle;
return handle;
}
void HAL_CleanPDP(HAL_PDPHandle handle) { HAL_CleanCAN(handle); }
void HAL_CleanPDP(HAL_PDPHandle handle) {
HAL_CleanCAN(handle);
for (int i = 0; i < kNumPDPModules; i++) {
if (pdpHandles[i] == handle) {
pdpHandles[i] = HAL_kInvalidHandle;
return;
}
}
}
HAL_Bool HAL_CheckPDPModule(int32_t module) {
return module < kNumPDPModules && module >= 0;

View File

@@ -604,7 +604,7 @@ void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {
spiSystem->strobeAutoForceOne(status);
}
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
int32_t numToRead, double timeout,
int32_t* status) {
std::lock_guard<wpi::mutex> lock(spiAutoMutex);

View File

@@ -215,9 +215,11 @@ void SerialHelper::QueryHubPaths(int32_t* status) {
if (matchString.equals(devNameRef)) continue;
// Search directories to get a list of system accessors
// The directories we need are not symbolic, so we can safely
// disable symbolic links.
std::error_code ec;
for (auto p = wpi::sys::fs::recursive_directory_iterator(
"/sys/devices/soc0", ec);
"/sys/devices/soc0", ec, false);
p != wpi::sys::fs::recursive_directory_iterator(); p.increment(ec)) {
if (ec) break;
wpi::StringRef path{p->path()};

View File

@@ -127,7 +127,7 @@ void ReportError(JNIEnv* env, int32_t status, bool doThrow) {
ThrowUncleanStatusException(env, buf.c_str(), status);
} else {
std::string func;
auto stack = GetJavaStackTrace(env, &func, "edu.wpi.first.wpilibj");
auto stack = GetJavaStackTrace(env, &func, "edu.wpi.first");
HAL_SendError(1, status, 0, message, func.c_str(), stack.c_str(), 1);
}
}

View File

@@ -236,9 +236,9 @@ Java_edu_wpi_first_hal_InterruptJNI_disableInterrupts
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: readInterruptRisingTimestamp
* Signature: (I)D
* Signature: (I)J
*/
JNIEXPORT jdouble JNICALL
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
(JNIEnv* env, jclass, jint interruptHandle)
{
@@ -248,7 +248,7 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
<< "Interrupt Handle = " << (HAL_InterruptHandle)interruptHandle;
int32_t status = 0;
jdouble timeStamp = HAL_ReadInterruptRisingTimestamp(
jlong timeStamp = HAL_ReadInterruptRisingTimestamp(
(HAL_InterruptHandle)interruptHandle, &status);
INTERRUPTJNI_LOG(logDEBUG) << "Status = " << status;
@@ -259,9 +259,9 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptRisingTimestamp
/*
* Class: edu_wpi_first_hal_InterruptJNI
* Method: readInterruptFallingTimestamp
* Signature: (I)D
* Signature: (I)J
*/
JNIEXPORT jdouble JNICALL
JNIEXPORT jlong JNICALL
Java_edu_wpi_first_hal_InterruptJNI_readInterruptFallingTimestamp
(JNIEnv* env, jclass, jint interruptHandle)
{
@@ -271,7 +271,7 @@ Java_edu_wpi_first_hal_InterruptJNI_readInterruptFallingTimestamp
<< "Interrupt Handle = " << (HAL_InterruptHandle)interruptHandle;
int32_t status = 0;
jdouble timeStamp = HAL_ReadInterruptFallingTimestamp(
jlong timeStamp = HAL_ReadInterruptFallingTimestamp(
(HAL_InterruptHandle)interruptHandle, &status);
INTERRUPTJNI_LOG(logDEBUG) << "Status = " << status;

View File

@@ -436,8 +436,8 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__ILjava_nio_ByteBuffer_2ID
SPIJNI_LOG(logDEBUG) << "Port = " << port;
SPIJNI_LOG(logDEBUG) << "NumToRead = " << numToRead;
SPIJNI_LOG(logDEBUG) << "Timeout = " << timeout;
uint8_t* recvBuf =
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
uint32_t* recvBuf =
reinterpret_cast<uint32_t*>(env->GetDirectBufferAddress(buffer));
int32_t status = 0;
jint retval = HAL_ReadSPIAutoReceivedData(
static_cast<HAL_SPIPort>(port), recvBuf, numToRead, timeout, &status);
@@ -450,18 +450,18 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__ILjava_nio_ByteBuffer_2ID
/*
* Class: edu_wpi_first_hal_SPIJNI
* Method: spiReadAutoReceivedData
* Signature: (I[BID)I
* Signature: (I[IID)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3BID
(JNIEnv* env, jclass, jint port, jbyteArray buffer, jint numToRead,
Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3IID
(JNIEnv* env, jclass, jint port, jintArray buffer, jint numToRead,
jdouble timeout)
{
SPIJNI_LOG(logDEBUG) << "Calling SPIJNI spiReadAutoReceivedData";
SPIJNI_LOG(logDEBUG) << "Port = " << port;
SPIJNI_LOG(logDEBUG) << "NumToRead = " << numToRead;
SPIJNI_LOG(logDEBUG) << "Timeout = " << timeout;
wpi::SmallVector<uint8_t, 128> recvBuf;
wpi::SmallVector<uint32_t, 128> recvBuf;
recvBuf.resize(numToRead);
int32_t status = 0;
jint retval =
@@ -471,8 +471,8 @@ Java_edu_wpi_first_hal_SPIJNI_spiReadAutoReceivedData__I_3BID
SPIJNI_LOG(logDEBUG) << "Return = " << retval;
if (!CheckStatus(env, status)) return retval;
if (numToRead > 0) {
env->SetByteArrayRegion(buffer, 0, numToRead,
reinterpret_cast<const jbyte*>(recvBuf.data()));
env->SetIntArrayRegion(buffer, 0, numToRead,
reinterpret_cast<const jint*>(recvBuf.data()));
}
return retval;
}

View File

@@ -39,6 +39,11 @@
#define ERR_CANSessionMux_NotAllowed_MESSAGE "CAN: Not allowed"
#define ERR_CANSessionMux_NotInitialized_MESSAGE "CAN: Not initialized"
#define ERR_FRCSystem_NetCommNotResponding_MESSAGE \
"FRCSystem: NetComm not responding"
#define ERR_FRCSystem_NoDSConnection_MESSAGE \
"FRCSystem: No driver station connected"
#define SAMPLE_RATE_TOO_HIGH 1001
#define SAMPLE_RATE_TOO_HIGH_MESSAGE \
"HAL: Analog module sample rate is too high"

View File

@@ -77,24 +77,28 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
/**
* Returns the timestamp for the rising interrupt that occurred most recently.
*
* This is in the same time domain as HAL_GetFPGATime().
* This is in the same time domain as HAL_GetFPGATime(). It only contains the
* bottom 32 bits of the timestamp. If your robot has been running for over 1
* hour, you will need to fill in the upper 32 bits yourself.
*
* @param interruptHandle the interrupt handle
* @return timestamp in seconds since FPGA Initialization
* @return timestamp in microseconds since FPGA Initialization
*/
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status);
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status);
/**
* Returns the timestamp for the falling interrupt that occurred most recently.
*
* This is in the same time domain as HAL_GetFPGATime().
* This is in the same time domain as HAL_GetFPGATime(). It only contains the
* bottom 32 bits of the timestamp. If your robot has been running for over 1
* hour, you will need to fill in the upper 32 bits yourself.
*
* @param interruptHandle the interrupt handle
* @return timestamp in seconds since FPGA Initialization
* @return timestamp in microseconds since FPGA Initialization
*/
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status);
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status);
/**
* Requests interrupts on a specific digital source.

View File

@@ -218,16 +218,20 @@ void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend,
void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status);
/**
* Reads data received by the SPI accumulator.
* Reads data received by the SPI accumulator. Each received data sequence
* consists of a timestamp followed by the received data bytes, one byte per
* word (in the least significant byte). The length of each received data
* sequence is the same as the combined dataSize + zeroSize set in
* HAL_SetSPIAutoTransmitData.
*
* @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4
* for MXP.
* @param buffer The buffer to store the data into.
* @param numToRead The number of bytes to read.
* @param numToRead The number of words to read.
* @param timeout The read timeout (in seconds).
* @return The number of bytes actually read.
* @return The number of words actually read.
*/
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
int32_t numToRead, double timeout,
int32_t* status);

View File

@@ -14,7 +14,7 @@
typedef void (*HAL_SpiReadAutoReceiveBufferCallback)(const char* name,
void* param,
unsigned char* buffer,
uint32_t* buffer,
int32_t numToRead,
int32_t* outputCount);

View File

@@ -103,6 +103,8 @@ class DriverStationSim {
HALSIM_SetDriverStationDsAttached(dsAttached);
}
void NotifyNewData() { HALSIM_NotifyDriverStationNewData(); }
void ResetData() { HALSIM_ResetDriverStationData(); }
};
} // namespace sim

View File

@@ -41,13 +41,11 @@ struct CANStorage {
static UnlimitedHandleResource<HAL_CANHandle, CANStorage, HAL_HandleEnum::CAN>*
canHandles;
static void CheckDeltaTime() {
// Noop on sim
}
static inline uint64_t ConvertToFPGATime(uint32_t canMs) {
uint64_t canMsToUs = canMs * 1000;
return canMsToUs;
static uint32_t GetPacketBaseTime() {
int status = 0;
auto basetime = HAL_GetFPGATime(&status);
// us to ms
return (basetime / 1000ull) & 0xFFFFFFFF;
}
namespace hal {
@@ -83,7 +81,6 @@ HAL_CANHandle HAL_InitializeCAN(HAL_CANManufacturer manufacturer,
int32_t deviceId, HAL_CANDeviceType deviceType,
int32_t* status) {
hal::init::CheckInit();
CheckDeltaTime();
auto can = std::make_shared<CANStorage>();
auto handle = canHandles->Allocate(can);
@@ -183,17 +180,16 @@ void HAL_ReadCANPacketNew(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
if (*status == 0) {
std::lock_guard<wpi::mutex> lock(can->mapMutex);
auto& msg = can->receives[messageId];
msg.length = dataSize;
msg.lastTimeStamp = timestamp;
msg.lastTimeStamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
}
*length = dataSize;
*receivedTimestamp = timestamp;
*receivedTimestamp = ts;
}
void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
@@ -210,21 +206,21 @@ void HAL_ReadCANPacketLatest(HAL_CANHandle handle, int32_t apiId, uint8_t* data,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;
@@ -247,29 +243,28 @@ void HAL_ReadCANPacketTimeout(HAL_CANHandle handle, int32_t apiId,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
// Found, check if new enough
uint64_t now = HAL_GetFPGATime(status);
if (now - i->second.lastTimeStamp >
static_cast<uint64_t>(timeoutMs) * 1000) {
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
// Timeout, return bad status
*status = HAL_CAN_TIMEOUT;
return;
}
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;
@@ -286,20 +281,22 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
*status = HAL_HANDLE_ERROR;
return;
}
uint32_t messageId = CreateCANId(can.get(), apiId);
{
std::lock_guard<wpi::mutex> lock(can->mapMutex);
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
uint64_t now = HAL_GetFPGATime(status);
// Found, check if new enough
if (now - i->second.lastTimeStamp <
static_cast<uint64_t>(periodMs) * 1000) {
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
*status = 0;
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
return;
}
}
}
@@ -308,29 +305,28 @@ void HAL_ReadCANPeriodicPacket(HAL_CANHandle handle, int32_t apiId,
uint32_t ts = 0;
HAL_CAN_ReceiveMessage(&messageId, 0x1FFFFFFF, data, &dataSize, &ts, status);
uint64_t timestamp = ConvertToFPGATime(ts);
std::lock_guard<wpi::mutex> lock(can->mapMutex);
if (*status == 0) {
// fresh update
auto& msg = can->receives[messageId];
msg.length = dataSize;
*length = dataSize;
msg.lastTimeStamp = timestamp;
*receivedTimestamp = timestamp;
msg.lastTimeStamp = ts;
*receivedTimestamp = ts;
// The NetComm call placed in data, copy into the msg
std::memcpy(msg.data, data, dataSize);
} else {
auto i = can->receives.find(messageId);
if (i != can->receives.end()) {
// Found, check if new enough
uint64_t now = HAL_GetFPGATime(status);
if (now - i->second.lastTimeStamp >
static_cast<uint64_t>(timeoutMs) * 1000) {
uint32_t now = GetPacketBaseTime();
if (now - i->second.lastTimeStamp > static_cast<uint32_t>(timeoutMs)) {
// Timeout, return bad status
*status = HAL_CAN_TIMEOUT;
return;
}
std::memcpy(i->second.data, data, i->second.length);
// Read the data from the stored message into the output
std::memcpy(data, i->second.data, i->second.length);
*length = i->second.length;
*receivedTimestamp = i->second.lastTimeStamp;
*status = 0;

View File

@@ -42,8 +42,8 @@ struct Interrupt {
uint8_t index;
HAL_AnalogTriggerType trigType;
bool watcher;
double risingTimestamp;
double fallingTimestamp;
int64_t risingTimestamp;
int64_t fallingTimestamp;
bool previousState;
bool fireOnUp;
bool fireOnDown;
@@ -238,10 +238,10 @@ static int64_t WaitForInterruptDigital(HAL_InterruptHandle handle,
// True => false, Falling
if (interrupt->previousState) {
// Set our return value and our timestamps
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
interrupt->fallingTimestamp = hal::GetFPGATime();
return 1 << (8 + interrupt->index);
} else {
interrupt->risingTimestamp = hal::GetFPGATimestamp();
interrupt->risingTimestamp = hal::GetFPGATime();
return 1 << (interrupt->index);
}
}
@@ -302,10 +302,10 @@ static int64_t WaitForInterruptAnalog(HAL_InterruptHandle handle,
// True => false, Falling
if (interrupt->previousState) {
// Set our return value and our timestamps
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
interrupt->fallingTimestamp = hal::GetFPGATime();
return 1 << (8 + interrupt->index);
} else {
interrupt->risingTimestamp = hal::GetFPGATimestamp();
interrupt->risingTimestamp = hal::GetFPGATime();
return 1 << (interrupt->index);
}
}
@@ -350,12 +350,12 @@ static void ProcessInterruptDigitalAsynchronous(const char* name, void* param,
int32_t mask = 0;
if (interrupt->previousState) {
interrupt->previousState = retVal;
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
interrupt->fallingTimestamp = hal::GetFPGATime();
mask = 1 << (8 + interrupt->index);
if (!interrupt->fireOnDown) return;
} else {
interrupt->previousState = retVal;
interrupt->risingTimestamp = hal::GetFPGATimestamp();
interrupt->risingTimestamp = hal::GetFPGATime();
mask = 1 << (interrupt->index);
if (!interrupt->fireOnUp) return;
}
@@ -385,12 +385,12 @@ static void ProcessInterruptAnalogAsynchronous(const char* name, void* param,
int mask = 0;
if (interrupt->previousState) {
interrupt->previousState = retVal;
interrupt->fallingTimestamp = hal::GetFPGATimestamp();
interrupt->fallingTimestamp = hal::GetFPGATime();
if (!interrupt->fireOnDown) return;
mask = 1 << (8 + interrupt->index);
} else {
interrupt->previousState = retVal;
interrupt->risingTimestamp = hal::GetFPGATimestamp();
interrupt->risingTimestamp = hal::GetFPGATime();
if (!interrupt->fireOnUp) return;
mask = 1 << (interrupt->index);
}
@@ -486,8 +486,8 @@ void HAL_DisableInterrupts(HAL_InterruptHandle interruptHandle,
}
interrupt->callbackId = -1;
}
double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
int64_t HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto interrupt = interruptHandles->Get(interruptHandle);
if (interrupt == nullptr) {
*status = HAL_HANDLE_ERROR;
@@ -496,8 +496,8 @@ double HAL_ReadInterruptRisingTimestamp(HAL_InterruptHandle interruptHandle,
return interrupt->risingTimestamp;
}
double HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
int64_t HAL_ReadInterruptFallingTimestamp(HAL_InterruptHandle interruptHandle,
int32_t* status) {
auto interrupt = interruptHandles->Get(interruptHandle);
if (interrupt == nullptr) {
*status = HAL_HANDLE_ERROR;

View File

@@ -54,7 +54,7 @@ void HAL_SetSPIAutoTransmitData(HAL_SPIPort port, const uint8_t* dataToSend,
int32_t dataSize, int32_t zeroSize,
int32_t* status) {}
void HAL_ForceSPIAutoRead(HAL_SPIPort port, int32_t* status) {}
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint8_t* buffer,
int32_t HAL_ReadSPIAutoReceivedData(HAL_SPIPort port, uint32_t* buffer,
int32_t numToRead, double timeout,
int32_t* status) {
return SimSPIData[port].ReadAutoReceivedData(buffer, numToRead, timeout,

View File

@@ -69,7 +69,7 @@ jint SimOnLoad(JavaVM* vm, void* reserved) {
spiReadAutoReceiveBufferCallbackCallback =
env->GetMethodID(spiReadAutoReceiveBufferCallbackCls, "callback",
"(Ljava/lang/String;[BI)I");
"(Ljava/lang/String;[II)I");
if (!spiReadAutoReceiveBufferCallbackCallback) return JNI_ERR;
InitializeStore();

View File

@@ -39,7 +39,7 @@ void SpiReadAutoReceiveBufferCallbackStore::create(JNIEnv* env, jobject obj) {
}
int32_t SpiReadAutoReceiveBufferCallbackStore::performCallback(
const char* name, unsigned char* buffer, int32_t numToRead) {
const char* name, uint32_t* buffer, int32_t numToRead) {
JNIEnv* env;
JavaVM* vm = sim::GetJVM();
bool didAttachThread = false;
@@ -58,15 +58,14 @@ int32_t SpiReadAutoReceiveBufferCallbackStore::performCallback(
wpi::outs().flush();
}
auto toCallbackArr =
MakeJByteArray(env, wpi::StringRef{reinterpret_cast<const char*>(buffer),
static_cast<size_t>(numToRead)});
auto toCallbackArr = MakeJIntArray(
env, wpi::ArrayRef<uint32_t>{buffer, static_cast<size_t>(numToRead)});
jint ret = env->CallIntMethod(m_call, sim::GetBufferCallback(),
MakeJString(env, name), toCallbackArr,
(jint)numToRead);
jbyte* fromCallbackArr = reinterpret_cast<jbyte*>(
jint* fromCallbackArr = reinterpret_cast<jint*>(
env->GetPrimitiveArrayCritical(toCallbackArr, nullptr));
for (int i = 0; i < ret; i++) {
@@ -106,7 +105,7 @@ SIM_JniHandle sim::AllocateSpiBufferCallback(
callbackStore->create(env, callback);
auto callbackFunc = [](const char* name, void* param, unsigned char* buffer,
auto callbackFunc = [](const char* name, void* param, uint32_t* buffer,
int32_t numToRead, int32_t* outputCount) {
uintptr_t handleTmp = reinterpret_cast<uintptr_t>(param);
SIM_JniHandle handle = static_cast<SIM_JniHandle>(handleTmp);

View File

@@ -22,7 +22,7 @@ namespace sim {
class SpiReadAutoReceiveBufferCallbackStore {
public:
void create(JNIEnv* env, jobject obj);
int32_t performCallback(const char* name, unsigned char* buffer,
int32_t performCallback(const char* name, uint32_t* buffer,
int32_t numToRead);
void free(JNIEnv* env);
void setCallbackId(int32_t id) { callbackId = id; }

View File

@@ -41,7 +41,7 @@ void DriverStationData::ResetData() {
test.Reset(false);
eStop.Reset(false);
fmsAttached.Reset(false);
dsAttached.Reset(false);
dsAttached.Reset(true);
allianceStationId.Reset(static_cast<HAL_AllianceStationID>(0));
matchTime.Reset(0.0);

View File

@@ -63,7 +63,7 @@ class DriverStationData {
SimDataValue<HAL_Bool, MakeBoolean, GetTestName> test{false};
SimDataValue<HAL_Bool, MakeBoolean, GetEStopName> eStop{false};
SimDataValue<HAL_Bool, MakeBoolean, GetFmsAttachedName> fmsAttached{false};
SimDataValue<HAL_Bool, MakeBoolean, GetDsAttachedName> dsAttached{false};
SimDataValue<HAL_Bool, MakeBoolean, GetDsAttachedName> dsAttached{true};
SimDataValue<HAL_AllianceStationID, MakeAllianceStationIdValue,
GetAllianceStationIdName>
allianceStationId{static_cast<HAL_AllianceStationID>(0)};

View File

@@ -46,7 +46,7 @@ int32_t SPIData::Transaction(const uint8_t* dataToSend, uint8_t* dataReceived,
return size;
}
int32_t SPIData::ReadAutoReceivedData(uint8_t* buffer, int32_t numToRead,
int32_t SPIData::ReadAutoReceivedData(uint32_t* buffer, int32_t numToRead,
double timeout, int32_t* status) {
int32_t outputCount = 0;
autoReceivedData(buffer, numToRead, &outputCount);

View File

@@ -24,7 +24,7 @@ class SPIData {
int32_t Write(const uint8_t* dataToSend, int32_t sendSize);
int32_t Transaction(const uint8_t* dataToSend, uint8_t* dataReceived,
int32_t size);
int32_t ReadAutoReceivedData(uint8_t* buffer, int32_t numToRead,
int32_t ReadAutoReceivedData(uint32_t* buffer, int32_t numToRead,
double timeout, int32_t* status);
SimDataValue<HAL_Bool, MakeBoolean, GetInitializedName> initialized{false};

View File

@@ -3,7 +3,6 @@ plugins {
id 'application'
id 'cpp'
id 'visual-studio'
id 'com.github.johnrengelman.shadow' version '2.0.3' apply false
}
apply plugin: 'edu.wpi.first.NativeUtils'

View File

@@ -21,6 +21,26 @@ model {
'_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
'_TI5?AVfailure']
}
ntcoreJNI(ExportsConfig) {
x86SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('NT_')) {
retList << symbol
}
}
return retList
}
x64SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('NT_')) {
retList << symbol
}
}
return retList
}
}
}
}

View File

@@ -22,14 +22,15 @@ using namespace nt;
void Dispatcher::StartServer(const Twine& persist_filename,
const char* listen_address, unsigned int port) {
std::string listen_address_copy(StringRef(listen_address).trim());
DispatcherBase::StartServer(
persist_filename,
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
static_cast<int>(port), listen_address, m_logger)));
static_cast<int>(port), listen_address_copy.c_str(), m_logger)));
}
void Dispatcher::SetServer(const char* server_name, unsigned int port) {
std::string server_name_copy(server_name);
std::string server_name_copy(StringRef(server_name).trim());
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
return wpi::TCPConnector::connect(server_name_copy.c_str(),
static_cast<int>(port), m_logger, 1);
@@ -40,7 +41,7 @@ void Dispatcher::SetServer(
ArrayRef<std::pair<StringRef, unsigned int>> servers) {
wpi::SmallVector<std::pair<std::string, int>, 16> servers_copy;
for (const auto& server : servers)
servers_copy.emplace_back(std::string{server.first},
servers_copy.emplace_back(std::string{server.first.trim()},
static_cast<int>(server.second));
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
@@ -94,7 +95,7 @@ void Dispatcher::SetServerTeam(unsigned int team, unsigned int port) {
}
void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) {
std::string server_name_copy(server_name);
std::string server_name_copy(StringRef(server_name).trim());
SetConnectorOverride([=]() -> std::unique_ptr<wpi::NetworkStream> {
return wpi::TCPConnector::connect(server_name_copy.c_str(),
static_cast<int>(port), m_logger, 1);

View File

@@ -11,7 +11,7 @@ using namespace nt;
std::atomic<int> InstanceImpl::s_default{-1};
std::atomic<InstanceImpl*> InstanceImpl::s_fast_instances[10];
wpi::UidVector<std::unique_ptr<InstanceImpl>, 10> InstanceImpl::s_instances;
wpi::UidVector<InstanceImpl*, 10> InstanceImpl::s_instances;
wpi::mutex InstanceImpl::s_mutex;
using namespace std::placeholders;
@@ -54,7 +54,7 @@ InstanceImpl* InstanceImpl::Get(int inst) {
// vector
if (static_cast<unsigned int>(inst) < s_instances.size()) {
return s_instances[inst].get();
return s_instances[inst];
}
// doesn't exist
@@ -84,9 +84,9 @@ int InstanceImpl::Alloc() {
}
int InstanceImpl::AllocImpl() {
unsigned int inst = s_instances.emplace_back();
unsigned int inst = s_instances.emplace_back(nullptr);
InstanceImpl* ptr = new InstanceImpl(inst);
s_instances[inst].reset(ptr);
s_instances[inst] = ptr;
if (inst < (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
s_fast_instances[inst] = ptr;
@@ -104,5 +104,6 @@ void InstanceImpl::Destroy(int inst) {
s_fast_instances[inst] = nullptr;
}
delete s_instances[inst];
s_instances.erase(inst);
}

View File

@@ -51,7 +51,7 @@ class InstanceImpl {
static std::atomic<int> s_default;
static std::atomic<InstanceImpl*> s_fast_instances[10];
static wpi::UidVector<std::unique_ptr<InstanceImpl>, 10> s_instances;
static wpi::UidVector<InstanceImpl*, 10> s_instances;
static wpi::mutex s_mutex;
};

View File

@@ -109,10 +109,6 @@ static wpi::StringRef UnescapeString(wpi::StringRef source,
continue;
}
switch (*++s) {
case '\\':
case '"':
buf.push_back(s[-1]);
break;
case 't':
buf.push_back('\t');
break;
@@ -133,7 +129,7 @@ static wpi::StringRef UnescapeString(wpi::StringRef source,
break;
}
default:
buf.push_back(s[-1]);
buf.push_back(*s);
break;
}
}

View File

@@ -194,6 +194,7 @@ NetworkTable::NetworkTable(NT_Inst inst, const Twine& path, const private_init&)
NetworkTable::~NetworkTable() {
for (auto& i : m_listeners) RemoveEntryListener(i.second);
for (auto i : m_lambdaListeners) RemoveEntryListener(i);
}
NetworkTableInstance NetworkTable::GetInstance() const {
@@ -297,6 +298,40 @@ void NetworkTable::AddSubTableListener(ITableListener* listener) {
AddSubTableListener(listener, false);
}
NT_EntryListener NetworkTable::AddSubTableListener(TableListener listener,
bool localNotify) {
size_t prefix_len = m_path.size() + 1;
// The lambda needs to be copyable, but StringMap is not, so use
// a shared_ptr to it.
auto notified_tables = std::make_shared<wpi::StringMap<char>>();
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
if (localNotify) flags |= NT_NOTIFY_LOCAL;
NT_EntryListener id = nt::AddEntryListener(
m_inst, m_path + Twine(PATH_SEPARATOR_CHAR),
[=](const EntryNotification& event) {
StringRef relative_key = event.name.substr(prefix_len);
auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
if (end_sub_table == StringRef::npos) return;
StringRef sub_table_key = relative_key.substr(0, end_sub_table);
if (notified_tables->find(sub_table_key) == notified_tables->end())
return;
notified_tables->insert(std::make_pair(sub_table_key, '\0'));
listener(this, sub_table_key, this->GetSubTable(sub_table_key));
},
flags);
m_lambdaListeners.emplace_back(id);
return id;
}
void NetworkTable::RemoveTableListener(NT_EntryListener listener) {
nt::RemoveEntryListener(listener);
auto matches_begin =
std::remove(m_lambdaListeners.begin(), m_lambdaListeners.end(), listener);
m_lambdaListeners.erase(matches_begin, m_lambdaListeners.end());
}
void NetworkTable::AddSubTableListener(ITableListener* listener,
bool localNotify) {
std::lock_guard<wpi::mutex> lock(m_mutex);

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