Compare commits

...

156 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
Peter Johnson
43d188a429 RobotBase: Call cscore Shutdown() from destructor 2018-11-09 23:33:55 -08:00
Peter Johnson
563d5334c9 Update OpenCV to 3.4.3-17 2018-11-09 23:33:55 -08:00
Peter Johnson
193b0a222c cscore: Add Shutdown() function
This allows ordered shutdown / destruction of sources and sinks prior to
global singleton destruction.
2018-11-09 23:33:55 -08:00
Peter Johnson
76f5d153fa wpiutil: Add Stop() to EventLoopRunner
This allows stopping the event loop without having to destroy the object.

Also fix ExecSync() to actually wait for the call to complete.
2018-11-09 23:33:55 -08:00
Peter Johnson
19caeca990 cscore: Use more standard naming for Instance public variables 2018-11-09 23:33:55 -08:00
Peter Johnson
0abae17653 cscore: Refactor sink and source creation
Also make sources and sinks members of Instance private, with appropriate
accessor functions.
2018-11-09 23:33:55 -08:00
Peter Johnson
81d10bc656 cscore: Build examples on Windows (#1421) 2018-11-09 22:42:04 -08:00
Austin Shalit
b51b86525d Stop webserver on test run (#1420) 2018-11-09 20:36:35 -08:00
Peter Johnson
ace37c517e CameraServer: enable usb cameras on non-Linux platforms (#1411) 2018-11-09 20:27:23 -08:00
Austin Schuh
ac751d3224 Fix unused variable warnings in ntcore (#1416)
This avoids -Wextra warnings.
2018-11-09 20:02:58 -08:00
Thad House
7c9a3c4d77 Update NI Library to 2019.7.1 and OpenCV to 3.4.3-16 (#1418)
Add def for each to eliminate duplication of version number.
2018-11-09 19:59:06 -08:00
Sam Carlberg
8be693f55d Fix list layout in shuffleboard example (#1413) 2018-11-09 01:14:15 -08:00
Peter Johnson
622ae29dff CameraServer: Change opencv sources to publish "cv:" type (#1412)
The LabVIEW dashboard has been fixed to understand this prefix.
2018-11-09 01:13:39 -08:00
Peter Johnson
e7c98feca2 libuv: Use WPI (FPGA) clock on roboRio (#1409)
This is set to the FPGA clock by HAL_Initialize.  Note this change means
that libuv loops should not be started until after HAL_Initialize is called (on the Rio).
Non-Rio functionality is unchanged.
2018-11-07 00:06:18 -08:00
Jaci Brunning
28087424ec Add deploy directory detection (#1400)
Add Filesystem class for java and namespace methods for C++ for detecting deploy location.
2018-11-02 13:16:44 -07:00
Peter Johnson
b6830638df NetworkListener: use Pimpl idiom (#1405)
This enables different platforms to use very different implementations.
2018-11-02 13:14:06 -07:00
Liam Kinne
fb557f49ea Add missing driver station documentation (#1388) 2018-11-01 23:34:48 -07:00
Peter Johnson
746f950a0b Remove ifdefs from linux NetworkListener (#1404) 2018-11-01 23:27:22 -07:00
Peter Johnson
9a38a3e188 Don't use static for raw_ostream outs/errs (#1401)
Static destruction order is not well defined, so it was possible for outs()
or errs() return value to be destroyed even while other code was running,
resulting in a crash.  Instead dynamically allocate the static so the
destructor never runs.  While this technically leaks, valgrind generally
supresses such leaks as the data is still "reachable" from the static pointer.
2018-11-01 10:48:50 -07:00
Thad House
2e3e3a47b9 Use a kill -9 after attemting a safe kill (#1397)
Should fix some deploy deadlocks.
2018-11-01 01:55:26 -07:00
Peter Johnson
e27d6d7bb8 cscore: Change impl to only one singleton (#1398)
This avoids a number of shutdown use-after-free races by controlling the
destruction order.  It also is a prerequisite to making the internal
interfaces mockable for unit testing.
2018-10-31 20:22:58 -07:00
Thad House
1dec0393a1 Fix static deletion race condition in DS thread (#1396)
The static condition variable was getting destroyed before the DS thread exited,
resulting in a deadlock on program exit when the DS thread tried to notify it.
This change moves the condition variable into the DS thread to avoid the race.
2018-10-30 11:51:17 -07:00
Peter Johnson
d03b020326 wpiutil: Add WorkerThread (#1302)
This provides a worker thread that can execute a work function with the result
going into either a future or a uv::Loop functor.
2018-10-29 20:54:42 -07:00
Austin Shalit
71e29b1d91 Remove unused import from rumble example (#1395)
This fixes a nit in #1394.
2018-10-29 20:54:15 -07:00
Thad House
f0b0965f9b Remove large HAL headers from wpilibc headers (#1386)
Now only includes the types in the wpilib headers.
Should immensely clean up intellisense.

Closes #1383
2018-10-29 12:49:17 -07:00
Austin Shalit
f774e47c80 Add an example showing how to use a hid rumbler (#1394) 2018-10-29 12:37:30 -07:00
Nicolas Machado
761933a164 Refactor Java Ultrasonic to use a List container (#1389) 2018-10-29 01:15:32 -07:00
Tyler Veness
99e0f08a6f Move applicable integration tests to native build as unit tests (#1364) 2018-10-29 00:12:38 -07:00
Tyler Veness
e89d5eb692 Fix stringop truncation warning from GCC 8.2 (#1393)
The next line adds a null terminator, but it's cleaner to just do a
std::memcpy() since the code already assumes a null terminator exists in
the source string.
2018-10-29 00:09:53 -07:00
Austin Shalit
2501e11886 Enable HTML5 javadocs (#1267)
This suppresses a build warning about HTML 4.01 being used.
2018-10-29 00:08:21 -07:00
Thad House
9174f23f36 Remove some usages of windows.h (#1370)
For HAL and wpilib, we don't need them, especially where they were being used.
2018-10-28 22:33:55 -07:00
Redrield
9f6544fa87 Allow binding commands to POV (#1378) 2018-10-28 21:54:06 -07:00
Peter Johnson
9a1af132bf Unify GetHostname() and use libuv implementation (#1391) 2018-10-28 19:01:48 -07:00
Thad House
a8aacd3657 Update build setup for raspbian and debug binaries (#1384)
- Build both debug and release binaries
- Append "d" to debug libraries in the style of opencv
- Split shared and static classifiers
- Add raspbian support
2018-10-27 00:19:38 -07:00
Peter Johnson
8ff81f5a2a cscore: Separate platform-specific sources (#1387) 2018-10-23 22:59:47 -07:00
Liam Kinne
349e273ecc AnalogGyro: add "calibrating for n seconds" message (#1380) 2018-10-23 00:29:23 -07:00
Tyler Veness
0a2ab4f0d7 Revert change in behavior in HeldButtonScheduler (#1381)
Originally, the command was restarted every time the scheduler was
executed if the button was pressed. #1340 changed this behavior in a
breaking manner.
2018-10-20 21:25:37 -07:00
461 changed files with 15342 additions and 3870 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

@@ -7,6 +7,8 @@ resources:
containers:
- container: wpilib2019
image: wpilib/roborio-cross-ubuntu:2019-18.04
- container: raspbian
image: wpilib/raspbian-cross-ubuntu:18.04
jobs:
- job: Linux_Arm
@@ -28,6 +30,25 @@ jobs:
# checkStyleRunAnalysis: true
# pmdRunAnalysis: true
- job: Linux_Raspbian
pool:
vmImage: 'Ubuntu 16.04'
container: raspbian
steps:
- task: Gradle@2
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
tasks: 'build'
options: '-PonlyRaspbian'
# checkStyleRunAnalysis: true
# pmdRunAnalysis: true
- job: Linux
pool:
vmImage: 'Ubuntu 16.04'
@@ -145,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 '1.7.7'
id 'edu.wpi.first.GradleJni' version '0.3.0'
id 'edu.wpi.first.GradleVsCode' version '0.4.2'
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.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 {
@@ -77,6 +79,12 @@ subprojects {
apply plugin: 'eclipse'
apply plugin: 'idea'
def subproj = it
plugins.withType(NativeComponentPlugin) {
subproj.apply plugin: MultiBuilds
}
apply from: "${rootDir}/shared/java/javastyle.gradle"
repositories {
@@ -92,5 +100,5 @@ subprojects {
}
wrapper {
gradleVersion = '4.9'
gradleVersion = '5.0'
}

View File

@@ -0,0 +1,95 @@
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.FileTree;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.language.base.internal.ProjectLayout;
import org.gradle.language.base.plugins.ComponentModelBasePlugin;
import org.gradle.language.nativeplatform.tasks.AbstractNativeSourceCompileTask;
import org.gradle.model.ModelMap;
import org.gradle.model.Mutate;
import org.gradle.nativeplatform.test.googletest.GoogleTestTestSuiteBinarySpec;
import org.gradle.model.RuleSource;
import org.gradle.model.Validate;
import org.gradle.nativeplatform.NativeExecutableBinarySpec
import org.gradle.nativeplatform.NativeBinarySpec;
import org.gradle.nativeplatform.NativeComponentSpec;
import org.gradle.nativeplatform.NativeLibrarySpec;
import org.gradle.nativeplatform.SharedLibraryBinarySpec;
import org.gradle.nativeplatform.StaticLibraryBinarySpec;
import org.gradle.nativeplatform.platform.internal.NativePlatformInternal;
import org.gradle.nativeplatform.toolchain.NativeToolChain;
import org.gradle.nativeplatform.toolchain.NativeToolChainRegistry;
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
import org.gradle.nativeplatform.toolchain.internal.ToolType;
import org.gradle.nativeplatform.toolchain.internal.gcc.AbstractGccCompatibleToolChain;
import org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppToolChain;
import org.gradle.nativeplatform.toolchain.internal.tools.ToolRegistry;
import org.gradle.platform.base.BinarySpec;
import org.gradle.platform.base.ComponentSpec;
import org.gradle.platform.base.ComponentSpecContainer;
import org.gradle.platform.base.BinaryContainer;
import org.gradle.platform.base.ComponentType;
import org.gradle.platform.base.TypeBuilder;
import org.gradle.nativeplatform.tasks.ObjectFilesToBinary;
import groovy.transform.CompileStatic;
import groovy.transform.CompileDynamic
import org.gradle.nativeplatform.BuildTypeContainer
@CompileStatic
class MultiBuilds implements Plugin<Project> {
@CompileStatic
public void apply(Project project) {
}
@CompileStatic
static class Rules extends RuleSource {
@Mutate
void setupBuildTypes(BuildTypeContainer buildTypes, ProjectLayout projectLayout) {
def project = (Project) projectLayout.projectIdentifier
if (project.hasProperty('releaseBuild')) {
buildTypes.create('debug')
} else {
buildTypes.create('release')
}
}
@CompileDynamic
private static void setBuildableFalseDynamically(NativeBinarySpec binary) {
binary.buildable = false
}
@Mutate
@CompileStatic
void disableReleaseGoogleTest(BinaryContainer binaries, ProjectLayout projectLayout) {
def project = (Project) projectLayout.projectIdentifier
if (project.hasProperty('testRelease')) {
return
}
binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
if (spec.buildType.name == 'release') {
Rules.setBuildableFalseDynamically(spec)
}
}
// def crossCompileConfigs = []
// for (BuildConfig config : configs) {
// if (!BuildConfigRulesBase.isCrossCompile(config)) {
// continue
// }
// crossCompileConfigs << config.architecture
// }
// if (!crossCompileConfigs.empty) {
// binaries.withType(GoogleTestTestSuiteBinarySpec) { oSpec ->
// GoogleTestTestSuiteBinarySpec spec = (GoogleTestTestSuiteBinarySpec) oSpec
// if (crossCompileConfigs.contains(spec.targetPlatform.architecture.name)) {
// setBuildableFalseDynamically(spec)
// }
// }
// }
}
}
}

View File

@@ -79,7 +79,8 @@ class SingleNativeBuild implements Plugin<Project> {
}
def tmpBaseBin = (NativeBinarySpec) oTmpBaseBin
if (tmpBaseBin.targetPlatform.operatingSystem.name == binary.targetPlatform.operatingSystem.name &&
tmpBaseBin.targetPlatform.architecture.name == binary.targetPlatform.architecture.name) {
tmpBaseBin.targetPlatform.architecture.name == binary.targetPlatform.architecture.name &&
tmpBaseBin.buildType == binary.buildType) {
baseBin = tmpBaseBin
}
}

View File

@@ -28,6 +28,7 @@ endif()
file(GLOB_RECURSE
cameraserver_native_src src/main/native/cpp/*.cpp)
add_library(cameraserver ${cameraserver_native_src})
set_target_properties(cameraserver PROPERTIES DEBUG_POSTFIX "d")
target_include_directories(cameraserver PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/cameraserver>)
@@ -50,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
@@ -85,9 +87,7 @@ public final class CameraServer {
}
}
case kCv:
// FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:".
// https://github.com/wpilibsuite/allwpilib/issues/407
return "usb:";
return "cv:";
default:
return "unknown:";
}
@@ -125,7 +125,7 @@ public final class CameraServer {
}
}
return values.toArray(String[]::new);
return values.toArray(new String[0]);
}
@SuppressWarnings({"JavadocMethod", "PMD.AvoidUsingHardCodedIP"})
@@ -159,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;
}
@@ -297,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;
@@ -539,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;
}
/**
@@ -597,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;
@@ -55,7 +56,6 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
CS_Status status = 0;
buf.clear();
switch (cs::GetSourceKind(source, &status)) {
#ifdef __linux__
case cs::VideoSource::kUsb: {
wpi::StringRef prefix{"usb:"};
buf.append(prefix.begin(), prefix.end());
@@ -63,7 +63,6 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
buf.append(path.begin(), path.end());
break;
}
#endif
case cs::VideoSource::kHttp: {
wpi::StringRef prefix{"ip:"};
buf.append(prefix.begin(), prefix.end());
@@ -72,9 +71,7 @@ static wpi::StringRef MakeSourceValue(CS_Source source,
break;
}
case cs::VideoSource::kCv:
// FIXME: Should be "cv:", but LabVIEW dashboard requires "usb:".
// https://github.com/wpilibsuite/allwpilib/issues/407
return "usb:";
return "cv:";
default:
return "unknown:";
}
@@ -160,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) {
@@ -457,7 +455,6 @@ CameraServer::CameraServer() : m_impl(new Impl) {}
CameraServer::~CameraServer() {}
#ifdef __linux__
cs::UsbCamera CameraServer::StartAutomaticCapture() {
cs::UsbCamera camera = StartAutomaticCapture(m_impl->m_defaultUsbDevice++);
auto csShared = GetCameraServerShared();
@@ -490,7 +487,6 @@ cs::UsbCamera CameraServer::StartAutomaticCapture(const wpi::Twine& name,
csShared->ReportUsbCamera(camera.GetHandle());
return camera;
}
#endif
cs::AxisCamera CameraServer::AddAxisCamera(const wpi::Twine& host) {
return AddAxisCamera("Axis Camera", host);
@@ -544,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

@@ -36,8 +36,6 @@ class CameraServer {
*/
static CameraServer* GetInstance();
#ifdef __linux__
// USBCamera does not work on anything except Linux.
/**
* Start automatically capturing images to send to the dashboard.
*
@@ -77,7 +75,6 @@ class CameraServer {
*/
cs::UsbCamera StartAutomaticCapture(const wpi::Twine& name,
const wpi::Twine& path);
#endif
/**
* Start automatically capturing images to send to the dashboard from
@@ -85,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.
@@ -176,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,6 +19,9 @@ licenseUpdateExclude {
includeGuardRoots {
cscore/src/main/native/cpp/
cscore/src/main/native/include/
cscore/src/main/native/linux/
cscore/src/main/native/osx/
cscore/src/main/native/windows/
cscore/src/main/test/native/cpp/
}

View File

@@ -4,6 +4,56 @@ include(SubDirList)
find_package( OpenCV REQUIRED )
file(GLOB
cscore_native_src src/main/native/cpp/*.cpp)
file(GLOB cscore_linux_src src/main/native/linux/*.cpp)
file(GLOB cscore_osx_src src/main/native/osx/*.cpp)
file(GLOB cscore_windows_src src/main/native/windows/*.cpp)
add_library(cscore ${cscore_native_src})
set_target_properties(cscore PROPERTIES DEBUG_POSTFIX "d")
if(NOT MSVC)
if (APPLE)
target_sources(cscore PRIVATE ${cscore_osx_src})
else()
target_sources(cscore PRIVATE ${cscore_linux_src})
endif()
else()
target_sources(cscore PRIVATE ${cscore_windows_src})
target_compile_options(cscore PUBLIC -DNOMINMAX)
target_compile_options(cscore PRIVATE -D_CRT_SECURE_NO_WARNINGS)
endif()
target_include_directories(cscore PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/cscore>)
target_include_directories(cscore PRIVATE src/main/native/cpp)
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
set_property(TARGET cscore PROPERTY FOLDER "libraries")
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
if (MSVC)
set (cscore_config_dir ${wpilib_dest})
else()
set (cscore_config_dir share/cscore)
endif()
install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
install(EXPORT cscore DESTINATION ${cscore_config_dir})
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
foreach(example ${cscore_examples})
file(GLOB cscore_example_src examples/${example}/*.cpp)
if(cscore_example_src)
add_executable(cscore_${example} ${cscore_example_src})
target_link_libraries(cscore_${example} cscore)
endif()
endforeach()
# Java bindings
if (NOT WITHOUT_JAVA)
find_package(Java REQUIRED)
@@ -50,49 +100,23 @@ if (NOT WITHOUT_JAVA)
set_property(TARGET cscore_jar PROPERTY FOLDER "java")
endif()
add_library(cscorejni ${cscore_jni_src})
target_link_libraries(cscorejni PUBLIC cscore wpiutil ${OpenCV_LIBS})
file(GLOB
cscore_native_src src/main/native/cpp/*.cpp)
add_library(cscore ${cscore_native_src} ${cscore_jni_src})
target_include_directories(cscore PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/cscore>)
target_link_libraries(cscore PUBLIC wpiutil ${OpenCV_LIBS})
set_property(TARGET cscorejni PROPERTY FOLDER "libraries")
set_property(TARGET cscore PROPERTY FOLDER "libraries")
if (NOT WITHOUT_JAVA)
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
target_include_directories(cscore PRIVATE ${JNI_INCLUDE_DIRS})
target_include_directories(cscore PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
target_include_directories(cscorejni PRIVATE ${JNI_INCLUDE_DIRS})
target_include_directories(cscorejni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
else()
target_link_libraries(cscore PRIVATE cscore_jni_headers)
target_link_libraries(cscorejni PRIVATE cscore_jni_headers)
endif()
add_dependencies(cscore cscore_jar)
endif()
add_dependencies(cscorejni cscore_jar)
install(TARGETS cscore EXPORT cscore DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/cscore")
if (NOT WITHOUT_JAVA AND MSVC)
install(TARGETS cscore RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
if (MSVC)
set (cscore_config_dir ${wpilib_dest})
else()
set (cscore_config_dir share/cscore)
endif()
install(FILES cscore-config.cmake DESTINATION ${cscore_config_dir})
install(EXPORT cscore DESTINATION ${cscore_config_dir})
SUBDIR_LIST(cscore_examples "${CMAKE_CURRENT_SOURCE_DIR}/examples")
foreach(example ${cscore_examples})
file(GLOB cscore_example_src examples/${example}/*.cpp)
if(cscore_example_src)
add_executable(cscore_${example} ${cscore_example_src})
target_link_libraries(cscore_${example} cscore)
if (MSVC)
install(TARGETS cscorejni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
endforeach()
install(TARGETS cscorejni EXPORT cscorejni DESTINATION "${main_lib_dest}")
endif()

View File

@@ -29,6 +29,43 @@ ext {
}
exportedHeaders {
srcDirs 'src/main/native/include'
include '**/*.h'
}
}
cscoreMacCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/osx'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
} else if (it.targetPlatform.operatingSystem.name == 'linux') {
it.sources {
cscoreLinuxCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/linux'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
} else if (it.targetPlatform.operatingSystem.name == 'windows') {
it.sources {
cscoreWindowsCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/windows'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/cpp'
include '**/*.h'
}
}
}
@@ -38,17 +75,15 @@ ext {
def examplesMap = [:];
if (OperatingSystem.current().isLinux()) {
File examplesTree = file("$projectDir/examples")
examplesTree.list(new FilenameFilter() {
@Override
public boolean accept(File current, String name) {
return new File(current, name).isDirectory();
}
}).each {
sharedCvConfigs.put(it, [])
examplesMap.put(it, [])
File examplesTree = file("$projectDir/examples")
examplesTree.list(new FilenameFilter() {
@Override
public boolean accept(File current, String name) {
return new File(current, name).isDirectory();
}
}).each {
sharedCvConfigs.put(it, [])
examplesMap.put(it, [])
}
apply from: "${rootDir}/shared/opencv.gradle"
@@ -63,17 +98,17 @@ 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_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
if (symbol.startsWith('CS_')) {
retList << symbol
}
}
@@ -82,7 +117,7 @@ model {
x64SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('CS_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
if (symbol.startsWith('CS_')) {
retList << symbol
}
}
@@ -93,6 +128,7 @@ model {
components {
examplesMap.each { key, value ->
"${key}"(NativeExecutableSpec) {
targetBuildTypes 'debug'
binaries.all {
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib library: 'cscore', linkage: 'shared'

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

@@ -24,7 +24,7 @@ public class CameraServerJNI {
static {
if (!libraryLoaded) {
try {
loader = new RuntimeLoader<>("cscore", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
loader = new RuntimeLoader<>("cscorejni", RuntimeLoader.getDefaultExtractionRoot(), CameraServerJNI.class);
loader.loadLibrary();
} catch (IOException ex) {
ex.printStackTrace();
@@ -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

@@ -13,6 +13,7 @@
#include <wpi/SmallString.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
@@ -20,14 +21,17 @@
using namespace cs;
CvSinkImpl::CvSinkImpl(const wpi::Twine& name) : SinkImpl{name} {
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry)
: SinkImpl{name, logger, notifier, telemetry} {
m_active = true;
// m_thread = std::thread(&CvSinkImpl::ThreadMain, this);
}
CvSinkImpl::CvSinkImpl(const wpi::Twine& name,
CvSinkImpl::CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
std::function<void(uint64_t time)> processFrame)
: SinkImpl{name} {}
: SinkImpl{name, logger, notifier, telemetry} {}
CvSinkImpl::~CvSinkImpl() { Stop(); }
@@ -119,24 +123,24 @@ void CvSinkImpl::ThreadMain() {
namespace cs {
CS_Sink CreateCvSink(const wpi::Twine& name, CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name);
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry));
}
CS_Sink CreateCvSinkCallback(const wpi::Twine& name,
std::function<void(uint64_t time)> processFrame,
CS_Status* status) {
auto sink = std::make_shared<CvSinkImpl>(name, processFrame);
auto handle = Sinks::GetInstance().Allocate(CS_SINK_CV, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
auto& inst = Instance::GetInstance();
return inst.CreateSink(
CS_SINK_CV, std::make_shared<CvSinkImpl>(name, inst.logger, inst.notifier,
inst.telemetry, processFrame));
}
void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -145,7 +149,7 @@ void SetSinkDescription(CS_Sink sink, const wpi::Twine& description,
}
uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -155,7 +159,7 @@ uint64_t GrabSinkFrame(CS_Sink sink, cv::Mat& image, CS_Status* status) {
uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -164,7 +168,7 @@ uint64_t GrabSinkFrameTimeout(CS_Sink sink, cv::Mat& image, double timeout,
}
std::string GetSinkError(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -174,7 +178,7 @@ std::string GetSinkError(CS_Sink sink, CS_Status* status) {
wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return wpi::StringRef{};
@@ -183,7 +187,7 @@ wpi::StringRef GetSinkError(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
}
void SetSinkEnabled(CS_Sink sink, bool enabled, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_CV) {
*status = CS_INVALID_HANDLE;
return;

View File

@@ -8,20 +8,17 @@
#ifndef CSCORE_CVSINKIMPL_H_
#define CSCORE_CVSINKIMPL_H_
#include <stdint.h>
#include <atomic>
#include <memory>
#include <functional>
#include <thread>
#include <vector>
#include <wpi/NetworkAcceptor.h>
#include <wpi/NetworkStream.h>
#include <wpi/SmallVector.h>
#include <wpi/StringRef.h>
#include <opencv2/core/core.hpp>
#include <wpi/Twine.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpi/raw_socket_ostream.h>
#include <wpi/condition_variable.h>
#include "Frame.h"
#include "SinkImpl.h"
namespace cs {
@@ -30,8 +27,10 @@ class SourceImpl;
class CvSinkImpl : public SinkImpl {
public:
explicit CvSinkImpl(const wpi::Twine& name);
CvSinkImpl(const wpi::Twine& name,
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry);
CvSinkImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry,
std::function<void(uint64_t time)> processFrame);
~CvSinkImpl() override;

View File

@@ -14,6 +14,7 @@
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "Notifier.h"
#include "c_util.h"
@@ -21,15 +22,21 @@
using namespace cs;
CvSourceImpl::CvSourceImpl(const wpi::Twine& name, const VideoMode& mode)
: SourceImpl{name} {
CvSourceImpl::CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const VideoMode& mode)
: SourceImpl{name, logger, notifier, telemetry} {
m_mode = mode;
m_videoModes.push_back(m_mode);
}
CvSourceImpl::~CvSourceImpl() {}
void CvSourceImpl::Start() {}
void CvSourceImpl::Start() {
m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
m_notifier.NotifySourceVideoMode(*this, m_mode);
}
bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
{
@@ -37,7 +44,7 @@ bool CvSourceImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
m_mode = mode;
m_videoModes[0] = mode;
}
Notifier::GetInstance().NotifySourceVideoMode(*this, mode);
m_notifier.NotifySourceVideoMode(*this, mode);
return true;
}
@@ -105,8 +112,8 @@ int CvSourceImpl::CreateProperty(const wpi::Twine& name, CS_PropertyKind kind,
prop.defaultValue = defaultValue;
value = prop.value;
});
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, ndx, kind, value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name, ndx,
kind, value, wpi::Twine{});
return ndx;
}
@@ -132,29 +139,23 @@ void CvSourceImpl::SetEnumPropertyChoices(int property,
return;
}
prop->enumChoices = choices;
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, prop->name, property,
CS_PROP_ENUM, prop->value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
prop->name, property, CS_PROP_ENUM,
prop->value, wpi::Twine{});
}
namespace cs {
CS_Source CreateCvSource(const wpi::Twine& name, const VideoMode& mode,
CS_Status* status) {
auto source = std::make_shared<CvSourceImpl>(name, mode);
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_CV, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
// Generate initial events here so they come after the source created event
source->Start(); // causes a property event
notifier.NotifySource(name, handle, CS_SOURCE_CONNECTED);
notifier.NotifySource(name, handle, CS_SOURCE_VIDEOMODES_UPDATED);
notifier.NotifySourceVideoMode(*source, mode);
return handle;
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_CV, std::make_shared<CvSourceImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, mode));
}
void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -164,7 +165,7 @@ void PutSourceFrame(CS_Source source, cv::Mat& image, CS_Status* status) {
void NotifySourceError(CS_Source source, const wpi::Twine& msg,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -173,7 +174,7 @@ void NotifySourceError(CS_Source source, const wpi::Twine& msg,
}
void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -183,7 +184,7 @@ void SetSourceConnected(CS_Source source, bool connected, CS_Status* status) {
void SetSourceDescription(CS_Source source, const wpi::Twine& description,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -195,7 +196,7 @@ CS_Property CreateSourceProperty(CS_Source source, const wpi::Twine& name,
CS_PropertyKind kind, int minimum, int maximum,
int step, int defaultValue, int value,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
@@ -210,7 +211,7 @@ CS_Property CreateSourcePropertyCallback(
CS_Source source, const wpi::Twine& name, CS_PropertyKind kind, int minimum,
int maximum, int step, int defaultValue, int value,
std::function<void(CS_Property property)> onChange, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return -1;
@@ -224,7 +225,7 @@ CS_Property CreateSourcePropertyCallback(
void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
wpi::ArrayRef<std::string> choices,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_CV) {
*status = CS_INVALID_HANDLE;
return;
@@ -237,7 +238,7 @@ void SetSourceEnumPropertyChoices(CS_Source source, CS_Property property,
*status = CS_INVALID_HANDLE;
return;
}
auto data2 = Sources::GetInstance().Get(Handle{i, Handle::kSource});
auto data2 = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data2 || data->source.get() != data2->source.get()) {
*status = CS_INVALID_HANDLE;
return;

View File

@@ -14,16 +14,21 @@
#include <string>
#include <vector>
#include <opencv2/core/core.hpp>
#include <wpi/ArrayRef.h>
#include <wpi/Twine.h>
#include "SourceImpl.h"
namespace cs {
class CvSourceImpl : public SourceImpl {
public:
CvSourceImpl(const wpi::Twine& name, const VideoMode& mode);
CvSourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, const VideoMode& mode);
~CvSourceImpl() override;
void Start();
void Start() override;
bool SetVideoMode(const VideoMode& mode, CS_Status* status) override;

View File

@@ -13,6 +13,7 @@
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "Instance.h"
#include "Log.h"
#include "SourceImpl.h"
@@ -450,9 +451,11 @@ Image* Frame::GetImageImpl(int width, int height,
if (!cur || cur->Is(width, height, pixelFormat, requiredJpegQuality))
return cur;
DEBUG4("converting image from "
<< cur->width << "x" << cur->height << " type " << cur->pixelFormat
<< " to " << width << "x" << height << " type " << pixelFormat);
WPI_DEBUG4(Instance::GetInstance().logger,
"converting image from " << cur->width << "x" << cur->height
<< " type " << cur->pixelFormat << " to "
<< width << "x" << height << " type "
<< pixelFormat);
// If the source image is a JPEG, we need to decode it before we can do
// anything else with it. Note that if the destination format is JPEG, we

View File

@@ -8,20 +8,10 @@
#ifndef CSCORE_HANDLE_H_
#define CSCORE_HANDLE_H_
#include <atomic>
#include <memory>
#include <utility>
#include <wpi/StringRef.h>
#include "UnlimitedHandleResource.h"
#include "cscore_c.h"
namespace cs {
class SinkImpl;
class SourceImpl;
// Handle data layout:
// Bits 0-15: Handle index
// Bits 16-23: Parent index (property only)
@@ -74,59 +64,6 @@ class Handle {
CS_Handle m_handle;
};
struct SourceData {
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
: kind{kind_}, refCount{0}, source{source_} {}
CS_SourceKind kind;
std::atomic_int refCount;
std::shared_ptr<SourceImpl> source;
};
class Sources
: public UnlimitedHandleResource<Handle, SourceData, Handle::kSource> {
public:
static Sources& GetInstance() {
static Sources instance;
return instance;
}
std::pair<CS_Source, std::shared_ptr<SourceData>> Find(
const SourceImpl& source) {
return FindIf(
[&](const SourceData& data) { return data.source.get() == &source; });
}
private:
Sources() = default;
};
struct SinkData {
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
CS_SinkKind kind;
std::atomic_int refCount;
std::atomic<CS_Source> sourceHandle;
std::shared_ptr<SinkImpl> sink;
};
class Sinks : public UnlimitedHandleResource<Handle, SinkData, Handle::kSink> {
public:
static Sinks& GetInstance() {
static Sinks instance;
return instance;
}
std::pair<CS_Sink, std::shared_ptr<SinkData>> Find(const SinkImpl& sink) {
return FindIf(
[&](const SinkData& data) { return data.sink.get() == &sink; });
}
private:
Sinks() = default;
};
} // namespace cs
#endif // CSCORE_HANDLE_H_

View File

@@ -13,6 +13,7 @@
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
@@ -21,12 +22,20 @@
using namespace cs;
HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind)
: SourceImpl{name}, m_kind{kind} {}
HttpCameraImpl::HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry)
: SourceImpl{name, logger, notifier, telemetry}, m_kind{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);
@@ -51,16 +60,32 @@ 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);
}
#ifdef __linux__
static inline void DoFdSet(int fd, fd_set* set, int* nfds) {
if (fd >= 0) {
FD_SET(fd, set);
if ((fd + 1) > *nfds) *nfds = fd + 1;
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");
}
#endif
void HttpCameraImpl::StreamThreadMain() {
while (m_active) {
@@ -92,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");
@@ -115,8 +144,8 @@ wpi::HttpConnection* HttpCameraImpl::DeviceStreamConnect(
}
// Try to connect
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
Logger::GetInstance(), 1);
auto stream =
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
if (!m_active || !stream) return nullptr;
@@ -126,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);
}
@@ -235,6 +265,7 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
}
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
wpi::Now());
++m_frameCount;
return true;
}
@@ -252,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;
}
@@ -277,8 +309,8 @@ void HttpCameraImpl::SettingsThreadMain() {
void HttpCameraImpl::DeviceSendSettings(wpi::HttpRequest& req) {
// Try to connect
auto stream = wpi::TCPConnector::connect(req.host.c_str(), req.port,
Logger::GetInstance(), 1);
auto stream =
wpi::TCPConnector::connect(req.host.c_str(), req.port, m_logger, 1);
if (!m_active || !stream) return;
@@ -341,9 +373,9 @@ void HttpCameraImpl::CreateProperty(const wpi::Twine& name,
name, httpParam, viaSettings, kind, minimum, maximum, step, defaultValue,
value));
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1, kind,
value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
m_propertyData.size() + 1, kind, value,
wpi::Twine{});
}
template <typename T>
@@ -359,12 +391,12 @@ void HttpCameraImpl::CreateEnumProperty(
enumChoices.clear();
for (const auto& choice : choices) enumChoices.emplace_back(choice);
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CREATED, name, m_propertyData.size() + 1,
CS_PROP_ENUM, value, wpi::Twine{});
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED, name,
m_propertyData.size() + 1, CS_PROP_ENUM, value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, name,
m_propertyData.size() + 1, CS_PROP_ENUM,
value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
name, m_propertyData.size() + 1, CS_PROP_ENUM,
value, wpi::Twine{});
}
std::unique_ptr<PropertyImpl> HttpCameraImpl::CreateEmptyProperty(
@@ -475,41 +507,38 @@ namespace cs {
CS_Source CreateHttpCamera(const wpi::Twine& name, const wpi::Twine& url,
CS_HttpCameraKind kind, CS_Status* status) {
auto& inst = Instance::GetInstance();
std::shared_ptr<HttpCameraImpl> source;
switch (kind) {
case CS_HTTP_AXIS:
source = std::make_shared<AxisCameraImpl>(name);
source = std::make_shared<AxisCameraImpl>(name, inst.logger,
inst.notifier, inst.telemetry);
break;
default:
source = std::make_shared<HttpCameraImpl>(name, kind);
source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
inst.notifier, inst.telemetry);
break;
}
if (!source->SetUrls(url.str(), status)) return 0;
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
source->Start();
return handle;
return inst.CreateSource(CS_SOURCE_HTTP, source);
}
CS_Source CreateHttpCamera(const wpi::Twine& name,
wpi::ArrayRef<std::string> urls,
CS_HttpCameraKind kind, CS_Status* status) {
auto& inst = Instance::GetInstance();
if (urls.empty()) {
*status = CS_EMPTY_VALUE;
return 0;
}
auto source = std::make_shared<HttpCameraImpl>(name, kind);
auto source = std::make_shared<HttpCameraImpl>(name, kind, inst.logger,
inst.notifier, inst.telemetry);
if (!source->SetUrls(urls, status)) return 0;
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_HTTP, source);
auto& notifier = Notifier::GetInstance();
notifier.NotifySource(name, handle, CS_SOURCE_CREATED);
source->Start();
return handle;
return inst.CreateSource(CS_SOURCE_HTTP, source);
}
CS_HttpCameraKind GetHttpCameraKind(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return CS_HTTP_UNKNOWN;
@@ -523,7 +552,7 @@ void SetHttpCameraUrls(CS_Source source, wpi::ArrayRef<std::string> urls,
*status = CS_EMPTY_VALUE;
return;
}
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return;
@@ -533,7 +562,7 @@ void SetHttpCameraUrls(CS_Source source, wpi::ArrayRef<std::string> urls,
std::vector<std::string> GetHttpCameraUrls(CS_Source source,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_HTTP) {
*status = CS_INVALID_HANDLE;
return std::vector<std::string>{};

View File

@@ -30,10 +30,11 @@ namespace cs {
class HttpCameraImpl : public SourceImpl {
public:
HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind);
HttpCameraImpl(const wpi::Twine& name, CS_HttpCameraKind kind,
wpi::Logger& logger, Notifier& notifier, Telemetry& telemetry);
~HttpCameraImpl() override;
void Start();
void Start() override;
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
@@ -110,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
@@ -129,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;
@@ -136,12 +143,15 @@ 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 {
public:
explicit AxisCameraImpl(const wpi::Twine& name)
: HttpCameraImpl{name, CS_HTTP_AXIS} {}
AxisCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry)
: HttpCameraImpl{name, CS_HTTP_AXIS, logger, notifier, telemetry} {}
#if 0
void SetProperty(int property, int value, CS_Status* status) override;
void SetStringProperty(int property, const wpi::Twine& value,

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. */
/*----------------------------------------------------------------------------*/
#include "Instance.h"
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
using namespace cs;
static void def_log_func(unsigned int level, const char* file,
unsigned int line, const char* msg) {
wpi::SmallString<128> buf;
wpi::raw_svector_ostream oss(buf);
if (level == 20) {
oss << "CS: " << msg << '\n';
wpi::errs() << oss.str();
return;
}
wpi::StringRef levelmsg;
if (level >= 50)
levelmsg = "CRITICAL: ";
else if (level >= 40)
levelmsg = "ERROR: ";
else if (level >= 30)
levelmsg = "WARNING: ";
else
return;
oss << "CS: " << levelmsg << msg << " (" << wpi::sys::path::filename(file)
<< ':' << line << ")\n";
wpi::errs() << oss.str();
}
Instance::Instance() : telemetry(notifier), networkListener(logger, notifier) {
SetDefaultLogger();
}
Instance::~Instance() {}
Instance& Instance::GetInstance() {
static Instance* inst = new Instance;
return *inst;
}
void Instance::Shutdown() {
eventLoop.Stop();
m_sinks.FreeAll();
m_sources.FreeAll();
networkListener.Stop();
telemetry.Stop();
notifier.Stop();
}
void Instance::SetDefaultLogger() { logger.SetLogger(def_log_func); }
std::pair<CS_Source, std::shared_ptr<SourceData>> Instance::FindSource(
const SourceImpl& source) {
return m_sources.FindIf(
[&](const SourceData& data) { return data.source.get() == &source; });
}
std::pair<CS_Sink, std::shared_ptr<SinkData>> Instance::FindSink(
const SinkImpl& sink) {
return m_sinks.FindIf(
[&](const SinkData& data) { return data.sink.get() == &sink; });
}
CS_Source Instance::CreateSource(CS_SourceKind kind,
std::shared_ptr<SourceImpl> source) {
auto handle = m_sources.Allocate(kind, source);
notifier.NotifySource(source->GetName(), handle, CS_SOURCE_CREATED);
source->Start();
return handle;
}
CS_Sink Instance::CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink) {
auto handle = m_sinks.Allocate(kind, sink);
notifier.NotifySink(sink->GetName(), handle, CS_SINK_CREATED);
return handle;
}
void Instance::DestroySource(CS_Source handle) {
if (auto data = m_sources.Free(handle))
notifier.NotifySource(data->source->GetName(), handle, CS_SOURCE_DESTROYED);
}
void Instance::DestroySink(CS_Sink handle) {
if (auto data = m_sinks.Free(handle))
notifier.NotifySink(data->sink->GetName(), handle, CS_SINK_DESTROYED);
}

View File

@@ -0,0 +1,115 @@
/*----------------------------------------------------------------------------*/
/* 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_INSTANCE_H_
#define CSCORE_INSTANCE_H_
#include <memory>
#include <utility>
#include <wpi/EventLoopRunner.h>
#include <wpi/Logger.h>
#include "Log.h"
#include "NetworkListener.h"
#include "Notifier.h"
#include "SinkImpl.h"
#include "SourceImpl.h"
#include "Telemetry.h"
#include "UnlimitedHandleResource.h"
namespace cs {
struct SourceData {
SourceData(CS_SourceKind kind_, std::shared_ptr<SourceImpl> source_)
: kind{kind_}, refCount{0}, source{source_} {}
CS_SourceKind kind;
std::atomic_int refCount;
std::shared_ptr<SourceImpl> source;
};
struct SinkData {
explicit SinkData(CS_SinkKind kind_, std::shared_ptr<SinkImpl> sink_)
: kind{kind_}, refCount{0}, sourceHandle{0}, sink{sink_} {}
CS_SinkKind kind;
std::atomic_int refCount;
std::atomic<CS_Source> sourceHandle;
std::shared_ptr<SinkImpl> sink;
};
class Instance {
public:
Instance(const Instance&) = delete;
Instance& operator=(const Instance&) = delete;
~Instance();
static Instance& GetInstance();
void Shutdown();
wpi::Logger logger;
Notifier notifier;
Telemetry telemetry;
NetworkListener networkListener;
private:
UnlimitedHandleResource<Handle, SourceData, Handle::kSource> m_sources;
UnlimitedHandleResource<Handle, SinkData, Handle::kSink> m_sinks;
public:
wpi::EventLoopRunner eventLoop;
std::pair<CS_Sink, std::shared_ptr<SinkData>> FindSink(const SinkImpl& sink);
std::pair<CS_Source, std::shared_ptr<SourceData>> FindSource(
const SourceImpl& source);
void SetDefaultLogger();
std::shared_ptr<SourceData> GetSource(CS_Source handle) {
return m_sources.Get(handle);
}
std::shared_ptr<SinkData> GetSink(CS_Sink handle) {
return m_sinks.Get(handle);
}
CS_Source CreateSource(CS_SourceKind kind,
std::shared_ptr<SourceImpl> source);
CS_Sink CreateSink(CS_SinkKind kind, std::shared_ptr<SinkImpl> sink);
void DestroySource(CS_Source handle);
void DestroySink(CS_Sink handle);
wpi::ArrayRef<CS_Source> EnumerateSourceHandles(
wpi::SmallVectorImpl<CS_Source>& vec) {
return m_sources.GetAll(vec);
}
wpi::ArrayRef<CS_Sink> EnumerateSinkHandles(
wpi::SmallVectorImpl<CS_Sink>& vec) {
return m_sinks.GetAll(vec);
}
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(
CS_Source source, wpi::SmallVectorImpl<CS_Sink>& vec) {
vec.clear();
m_sinks.ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
});
return vec;
}
private:
Instance();
};
} // namespace cs
#endif // CSCORE_INSTANCE_H_

View File

@@ -1,45 +0,0 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#include "Log.h"
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/raw_ostream.h>
using namespace cs;
static void def_log_func(unsigned int level, const char* file,
unsigned int line, const char* msg) {
wpi::SmallString<128> buf;
wpi::raw_svector_ostream oss(buf);
if (level == 20) {
oss << "CS: " << msg << '\n';
wpi::errs() << oss.str();
return;
}
wpi::StringRef levelmsg;
if (level >= 50)
levelmsg = "CRITICAL: ";
else if (level >= 40)
levelmsg = "ERROR: ";
else if (level >= 30)
levelmsg = "WARNING: ";
else
return;
oss << "CS: " << levelmsg << msg << " (" << wpi::sys::path::filename(file)
<< ':' << line << ")\n";
wpi::errs() << oss.str();
}
Logger::Logger() { SetDefaultLogger(); }
Logger::~Logger() {}
void Logger::SetDefaultLogger() { SetLogger(def_log_func); }

View File

@@ -10,34 +10,18 @@
#include <wpi/Logger.h>
namespace cs {
class Logger : public wpi::Logger {
public:
static Logger& GetInstance() {
static Logger instance;
return instance;
}
~Logger();
void SetDefaultLogger();
private:
Logger();
};
#define LOG(level, x) WPI_LOG(cs::Logger::GetInstance(), level, x)
#define LOG(level, x) WPI_LOG(m_logger, level, x)
#undef ERROR
#define ERROR(x) WPI_ERROR(cs::Logger::GetInstance(), x)
#define WARNING(x) WPI_WARNING(cs::Logger::GetInstance(), x)
#define INFO(x) WPI_INFO(cs::Logger::GetInstance(), x)
#define ERROR(x) WPI_ERROR(m_logger, x)
#define WARNING(x) WPI_WARNING(m_logger, x)
#define INFO(x) WPI_INFO(m_logger, x)
#define DEBUG(x) WPI_DEBUG(cs::Logger::GetInstance(), x)
#define DEBUG1(x) WPI_DEBUG1(cs::Logger::GetInstance(), x)
#define DEBUG2(x) WPI_DEBUG2(cs::Logger::GetInstance(), x)
#define DEBUG3(x) WPI_DEBUG3(cs::Logger::GetInstance(), x)
#define DEBUG4(x) WPI_DEBUG4(cs::Logger::GetInstance(), x)
#define DEBUG(x) WPI_DEBUG(m_logger, x)
#define DEBUG1(x) WPI_DEBUG1(m_logger, x)
#define DEBUG2(x) WPI_DEBUG2(m_logger, x)
#define DEBUG3(x) WPI_DEBUG3(m_logger, x)
#define DEBUG4(x) WPI_DEBUG4(m_logger, x)
#define SERROR(x) ERROR(GetName() << ": " << x)
#define SWARNING(x) WARNING(GetName() << ": " << x)
@@ -49,6 +33,4 @@ class Logger : public wpi::Logger {
#define SDEBUG3(x) DEBUG3(GetName() << ": " << x)
#define SDEBUG4(x) DEBUG4(GetName() << ": " << x)
} // namespace cs
#endif // CSCORE_LOG_H_

View File

@@ -16,6 +16,7 @@
#include <wpi/raw_socket_ostream.h>
#include "Handle.h"
#include "Instance.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
@@ -66,14 +67,16 @@ 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>";
class MjpegServerImpl::ConnThread : public wpi::SafeThread {
public:
explicit ConnThread(const wpi::Twine& name) : m_name(name.str()) {}
explicit ConnThread(const wpi::Twine& name, wpi::Logger& logger)
: m_name(name.str()), m_logger(logger) {}
void Main();
@@ -97,6 +100,7 @@ class MjpegServerImpl::ConnThread : public wpi::SafeThread {
private:
std::string m_name;
wpi::Logger& m_logger;
wpi::StringRef GetName() { return m_name; }
@@ -107,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;
}
};
@@ -331,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;
@@ -409,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>"
@@ -450,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;
@@ -548,10 +562,11 @@ void MjpegServerImpl::ConnThread::SendJSON(wpi::raw_ostream& os,
os.flush();
}
MjpegServerImpl::MjpegServerImpl(const wpi::Twine& name,
MjpegServerImpl::MjpegServerImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const wpi::Twine& listenAddress, int port,
std::unique_ptr<wpi::NetworkAcceptor> acceptor)
: SinkImpl{name},
: SinkImpl{name, logger, notifier, telemetry},
m_listenAddress(listenAddress.str()),
m_port(port),
m_acceptor{std::move(acceptor)} {
@@ -628,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()) {
@@ -650,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();
@@ -690,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"
@@ -724,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;
@@ -744,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;
@@ -802,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");
@@ -865,7 +911,7 @@ void MjpegServerImpl::ServerThreadMain() {
}
// Start it if not already started
it->Start(GetName());
it->Start(GetName(), m_logger);
auto nstreams =
std::count_if(m_connThreads.begin(), m_connThreads.end(),
@@ -909,20 +955,20 @@ namespace cs {
CS_Sink CreateMjpegServer(const wpi::Twine& name,
const wpi::Twine& listenAddress, int port,
CS_Status* status) {
auto& inst = Instance::GetInstance();
wpi::SmallString<128> listenAddressBuf;
auto sink = std::make_shared<MjpegServerImpl>(
name, listenAddress, port,
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
port,
listenAddress.toNullTerminatedStringRef(listenAddressBuf).data(),
Logger::GetInstance())));
auto handle = Sinks::GetInstance().Allocate(CS_SINK_MJPEG, sink);
Notifier::GetInstance().NotifySink(name, handle, CS_SINK_CREATED);
return handle;
return inst.CreateSink(
CS_SINK_MJPEG,
std::make_shared<MjpegServerImpl>(
name, inst.logger, inst.notifier, inst.telemetry, listenAddress, port,
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
port,
listenAddress.toNullTerminatedStringRef(listenAddressBuf).data(),
inst.logger))));
}
std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_MJPEG) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -931,7 +977,7 @@ std::string GetMjpegServerListenAddress(CS_Sink sink, CS_Status* status) {
}
int GetMjpegServerPort(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data || data->kind != CS_SINK_MJPEG) {
*status = CS_INVALID_HANDLE;
return 0;

View File

@@ -31,8 +31,10 @@ class SourceImpl;
class MjpegServerImpl : public SinkImpl {
public:
MjpegServerImpl(const wpi::Twine& name, const wpi::Twine& listenAddress,
int port, std::unique_ptr<wpi::NetworkAcceptor> acceptor);
MjpegServerImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const wpi::Twine& listenAddress, int port,
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
~MjpegServerImpl() override;
void Stop();

View File

@@ -8,26 +8,25 @@
#ifndef CSCORE_NETWORKLISTENER_H_
#define CSCORE_NETWORKLISTENER_H_
#include <wpi/SafeThread.h>
#include <memory>
#include <wpi/Logger.h>
namespace cs {
class Notifier;
class NetworkListener {
public:
static NetworkListener& GetInstance() {
static NetworkListener instance;
return instance;
}
NetworkListener(wpi::Logger& logger, Notifier& notifier);
~NetworkListener();
void Start();
void Stop();
private:
NetworkListener() = default;
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;
class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace cs

View File

@@ -11,6 +11,7 @@
#include <vector>
#include "Handle.h"
#include "Instance.h"
#include "SinkImpl.h"
#include "SourceImpl.h"
@@ -157,7 +158,7 @@ void Notifier::NotifySource(const wpi::Twine& name, CS_Source source,
}
void Notifier::NotifySource(const SourceImpl& source, CS_EventKind kind) {
auto handleData = Sources::GetInstance().Find(source);
auto handleData = Instance::GetInstance().FindSource(source);
NotifySource(source.GetName(), handleData.first, kind);
}
@@ -166,7 +167,7 @@ void Notifier::NotifySourceVideoMode(const SourceImpl& source,
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_notifications.emplace(source.GetName(), handleData.first, mode);
thr->m_cond.notify_one();
@@ -179,7 +180,7 @@ void Notifier::NotifySourceProperty(const SourceImpl& source, CS_EventKind kind,
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_notifications.emplace(
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),
@@ -198,7 +199,7 @@ void Notifier::NotifySink(const wpi::Twine& name, CS_Sink sink,
}
void Notifier::NotifySink(const SinkImpl& sink, CS_EventKind kind) {
auto handleData = Sinks::GetInstance().Find(sink);
auto handleData = Instance::GetInstance().FindSink(sink);
NotifySink(sink.GetName(), handleData.first, kind);
}
@@ -221,7 +222,7 @@ void Notifier::NotifySinkProperty(const SinkImpl& sink, CS_EventKind kind,
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sinks::GetInstance().Find(sink);
auto handleData = Instance::GetInstance().FindSink(sink);
thr->m_notifications.emplace(
propertyName, handleData.first, static_cast<RawEvent::Kind>(kind),

View File

@@ -23,10 +23,7 @@ class Notifier {
friend class NotifierTest;
public:
static Notifier& GetInstance() {
static Notifier instance;
return instance;
}
Notifier();
~Notifier();
void Start();
@@ -62,8 +59,6 @@ class Notifier {
void NotifyTelemetryUpdated();
private:
Notifier();
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;

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,12 +7,20 @@
#include "SinkImpl.h"
#include <wpi/json.h>
#include "Instance.h"
#include "Notifier.h"
#include "SourceImpl.h"
using namespace cs;
SinkImpl::SinkImpl(const wpi::Twine& name) : m_name{name.str()} {}
SinkImpl::SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry)
: m_logger(logger),
m_notifier(notifier),
m_telemetry(telemetry),
m_name{name.str()} {}
SinkImpl::~SinkImpl() {
if (m_source) {
@@ -37,7 +45,7 @@ void SinkImpl::Enable() {
++m_enabledCount;
if (m_enabledCount == 1) {
if (m_source) m_source->EnableSink();
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
}
}
@@ -46,7 +54,7 @@ void SinkImpl::Disable() {
--m_enabledCount;
if (m_enabledCount == 0) {
if (m_source) m_source->DisableSink();
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
}
}
@@ -55,11 +63,11 @@ void SinkImpl::SetEnabled(bool enabled) {
if (enabled && m_enabledCount == 0) {
if (m_source) m_source->EnableSink();
m_enabledCount = 1;
Notifier::GetInstance().NotifySink(*this, CS_SINK_ENABLED);
m_notifier.NotifySink(*this, CS_SINK_ENABLED);
} else if (!enabled && m_enabledCount > 0) {
if (m_source) m_source->DisableSink();
m_enabledCount = 0;
Notifier::GetInstance().NotifySink(*this, CS_SINK_DISABLED);
m_notifier.NotifySink(*this, CS_SINK_DISABLED);
}
}
@@ -96,16 +104,52 @@ 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) {
auto& notifier = Notifier::GetInstance();
notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,
prop.valueStr);
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,
prop.valueStr);
// also notify choices updated event for enum types
if (prop.propKind == CS_PROP_ENUM)
notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED,
prop.name, propIndex, prop.propKind, prop.value,
wpi::Twine{});
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_CHOICES_UPDATED,
prop.name, propIndex, prop.propKind,
prop.value, wpi::Twine{});
}
void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
@@ -119,10 +163,11 @@ void SinkImpl::UpdatePropertyValue(int property, bool setString, int value,
prop->SetValue(value);
// Only notify updates after we've notified created
if (m_properties_cached)
Notifier::GetInstance().NotifySinkProperty(
*this, CS_SINK_PROPERTY_VALUE_UPDATED, prop->name, property,
prop->propKind, prop->value, prop->valueStr);
if (m_properties_cached) {
m_notifier.NotifySinkProperty(*this, CS_SINK_PROPERTY_VALUE_UPDATED,
prop->name, property, prop->propKind,
prop->value, prop->valueStr);
}
}
void SinkImpl::SetSourceImpl(std::shared_ptr<SourceImpl> source) {}

View File

@@ -11,19 +11,27 @@
#include <memory>
#include <string>
#include <wpi/Logger.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpi/mutex.h>
#include "SourceImpl.h"
namespace wpi {
class json;
} // namespace wpi
namespace cs {
class Frame;
class Notifier;
class Telemetry;
class SinkImpl : public PropertyContainer {
public:
explicit SinkImpl(const wpi::Twine& name);
explicit SinkImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry);
virtual ~SinkImpl();
SinkImpl(const SinkImpl& queue) = delete;
SinkImpl& operator=(const SinkImpl& queue) = delete;
@@ -47,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;
@@ -55,6 +68,11 @@ class SinkImpl : public PropertyContainer {
virtual void SetSourceImpl(std::shared_ptr<SourceImpl> source);
protected:
wpi::Logger& m_logger;
Notifier& m_notifier;
Telemetry& m_telemetry;
private:
std::string m_name;
std::string m_description;

View File

@@ -11,6 +11,7 @@
#include <cstring>
#include <wpi/STLExtras.h>
#include <wpi/json.h>
#include <wpi/timestamp.h>
#include "Log.h"
@@ -21,7 +22,12 @@ using namespace cs;
static constexpr size_t kMaxImagesAvail = 32;
SourceImpl::SourceImpl(const wpi::Twine& name) : m_name{name.str()} {
SourceImpl::SourceImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry)
: m_logger(logger),
m_notifier(notifier),
m_telemetry(telemetry),
m_name{name.str()} {
m_frame = Frame{*this, wpi::StringRef{}, 0};
}
@@ -53,9 +59,9 @@ wpi::StringRef SourceImpl::GetDescription(
void SourceImpl::SetConnected(bool connected) {
bool wasConnected = m_connected.exchange(connected);
if (wasConnected && !connected)
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_DISCONNECTED);
m_notifier.NotifySource(*this, CS_SOURCE_DISCONNECTED);
else if (!wasConnected && connected)
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_CONNECTED);
m_notifier.NotifySource(*this, CS_SOURCE_CONNECTED);
}
uint64_t SourceImpl::GetCurFrameTime() {
@@ -156,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))
@@ -215,9 +436,8 @@ void SourceImpl::PutFrame(VideoMode::PixelFormat pixelFormat, int width,
void SourceImpl::PutFrame(std::unique_ptr<Image> image, Frame::Time time) {
// Update telemetry
Telemetry::GetInstance().RecordSourceFrames(*this, 1);
Telemetry::GetInstance().RecordSourceBytes(*this,
static_cast<int>(image->size()));
m_telemetry.RecordSourceFrames(*this, 1);
m_telemetry.RecordSourceBytes(*this, static_cast<int>(image->size()));
// Update frame
{
@@ -241,15 +461,14 @@ void SourceImpl::PutError(const wpi::Twine& msg, Frame::Time time) {
}
void SourceImpl::NotifyPropertyCreated(int propIndex, PropertyImpl& prop) {
auto& notifier = Notifier::GetInstance();
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,
prop.valueStr);
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CREATED, prop.name,
propIndex, prop.propKind, prop.value,
prop.valueStr);
// also notify choices updated event for enum types
if (prop.propKind == CS_PROP_ENUM)
notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
prop.name, propIndex, prop.propKind,
prop.value, wpi::Twine{});
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_CHOICES_UPDATED,
prop.name, propIndex, prop.propKind,
prop.value, wpi::Twine{});
}
void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
@@ -263,10 +482,11 @@ void SourceImpl::UpdatePropertyValue(int property, bool setString, int value,
prop->SetValue(value);
// Only notify updates after we've notified created
if (m_properties_cached)
Notifier::GetInstance().NotifySourceProperty(
*this, CS_SOURCE_PROPERTY_VALUE_UPDATED, prop->name, property,
prop->propKind, prop->value, prop->valueStr);
if (m_properties_cached) {
m_notifier.NotifySourceProperty(*this, CS_SOURCE_PROPERTY_VALUE_UPDATED,
prop->name, property, prop->propKind,
prop->value, prop->valueStr);
}
}
void SourceImpl::ReleaseImage(std::unique_ptr<Image> image) {

View File

@@ -15,27 +15,39 @@
#include <vector>
#include <wpi/ArrayRef.h>
#include <wpi/Logger.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include "Frame.h"
#include "Handle.h"
#include "Image.h"
#include "PropertyContainer.h"
#include "cscore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace cs {
class Notifier;
class Telemetry;
class SourceImpl : public PropertyContainer {
friend class Frame;
public:
explicit SourceImpl(const wpi::Twine& name);
SourceImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry);
virtual ~SourceImpl();
SourceImpl(const SourceImpl& oth) = delete;
SourceImpl& operator=(const SourceImpl& oth) = delete;
virtual void Start() = 0;
wpi::StringRef GetName() const { return m_name; }
void SetDescription(const wpi::Twine& description);
@@ -119,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,
@@ -146,6 +163,10 @@ class SourceImpl : public PropertyContainer {
// Current video mode
mutable VideoMode m_mode;
wpi::Logger& m_logger;
Notifier& m_notifier;
Telemetry& m_telemetry;
private:
void ReleaseImage(std::unique_ptr<Image> image);
std::unique_ptr<Frame::Impl> AllocFrameImpl();

View File

@@ -14,15 +14,20 @@
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "Notifier.h"
#include "SourceImpl.h"
#include "cscore_cpp.h"
using namespace cs;
class Telemetry::Thread : public wpi::SafeThread {
public:
explicit Thread(Notifier& notifier) : m_notifier(notifier) {}
void Main();
Notifier& m_notifier;
wpi::DenseMap<std::pair<CS_Handle, int>, int64_t> m_user;
wpi::DenseMap<std::pair<CS_Handle, int>, int64_t> m_current;
double m_period = 0.0;
@@ -41,11 +46,9 @@ int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
return it->getSecond();
}
Telemetry::Telemetry() {}
Telemetry::~Telemetry() {}
void Telemetry::Start() { m_owner.Start(); }
void Telemetry::Start() { m_owner.Start(m_notifier); }
void Telemetry::Stop() { m_owner.Stop(); }
@@ -74,7 +77,7 @@ void Telemetry::Thread::Main() {
prevTime = curTime;
// notify
Notifier::GetInstance().NotifyTelemetryUpdated();
m_notifier.NotifyTelemetryUpdated();
}
}
@@ -117,7 +120,7 @@ double Telemetry::GetAverageValue(CS_Handle handle, CS_TelemetryKind kind,
void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
quantity;
@@ -126,7 +129,7 @@ void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
auto thr = m_owner.GetThread();
if (!thr) return;
auto handleData = Sources::GetInstance().Find(source);
auto handleData = Instance::GetInstance().FindSource(source);
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
quantity;

View File

@@ -14,16 +14,14 @@
namespace cs {
class Notifier;
class SourceImpl;
class Telemetry {
friend class TelemetryTest;
public:
static Telemetry& GetInstance() {
static Telemetry instance;
return instance;
}
explicit Telemetry(Notifier& notifier) : m_notifier(notifier) {}
~Telemetry();
void Start();
@@ -41,7 +39,7 @@ class Telemetry {
void RecordSourceFrames(const SourceImpl& source, int quantity);
private:
Telemetry();
Notifier& m_notifier;
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;

View File

@@ -50,11 +50,13 @@ class UnlimitedHandleResource {
std::shared_ptr<TStruct> Get(THandle handle);
void Free(THandle handle);
std::shared_ptr<TStruct> Free(THandle handle);
template <typename T>
wpi::ArrayRef<T> GetAll(wpi::SmallVectorImpl<T>& vec);
std::vector<std::shared_ptr<TStruct>> FreeAll();
// @param func functor with (THandle, const TStruct&) parameters
template <typename F>
void ForEach(F func);
@@ -121,14 +123,17 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Get(
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
inline void UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
inline std::shared_ptr<TStruct>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::Free(
THandle handle) {
auto index =
handle.GetTypedIndex(static_cast<typename THandle::Type>(typeValue));
if (index < 0) return;
if (index < 0) return nullptr;
std::lock_guard<TMutex> sync(m_handleMutex);
if (index >= static_cast<int>(m_structures.size())) return;
if (index >= static_cast<int>(m_structures.size())) return nullptr;
auto rv = std::move(m_structures[index]);
m_structures[index].reset();
return rv;
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
@@ -140,6 +145,15 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::GetAll(
return vec;
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
inline std::vector<std::shared_ptr<TStruct>>
UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FreeAll() {
std::lock_guard<TMutex> sync(m_handleMutex);
auto rv = std::move(m_structures);
m_structures.clear();
return rv;
}
template <typename THandle, typename TStruct, int typeValue, typename TMutex>
template <typename F>
inline void
@@ -163,20 +177,6 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
return std::make_pair(0, nullptr);
}
template <typename THandle, typename TStruct, int typeValue,
typename TMutex = wpi::mutex>
class StaticUnlimitedHandleResource
: public UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex> {
public:
static StaticUnlimitedHandleResource& GetInstance() {
static StaticUnlimitedHandleResource instance;
return instance;
}
private:
StaticUnlimitedHandleResource() = default;
};
} // namespace cs
#endif // CSCORE_UNLIMITEDHANDLERESOURCE_H_

View File

@@ -0,0 +1,79 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_c.h" // NOLINT(build/include_order)
#include "c_util.h"
#include "cscore_cpp.h"
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) {
return cs::CreateUsbCameraDev(name, dev, status);
}
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Status* status) {
return cs::CreateUsbCameraPath(name, path, status);
}
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) 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) 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);
}
@@ -348,6 +366,8 @@ void CS_SetDefaultLogger(unsigned int min_level) {
cs::SetDefaultLogger(min_level);
}
void CS_Shutdown(void) { cs::Shutdown(); }
CS_Source* CS_EnumerateSources(int* count, CS_Status* status) {
wpi::SmallVector<CS_Source, 32> buf;
auto handles = cs::EnumerateSourceHandles(buf, status);

View File

@@ -7,16 +7,12 @@
#include "cscore_cpp.h"
#if defined(__linux__)
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <wpi/SmallString.h>
#include <wpi/hostname.h>
#include <wpi/json.h>
#include "Handle.h"
#include "Instance.h"
#include "Log.h"
#include "NetworkListener.h"
#include "Notifier.h"
@@ -33,7 +29,7 @@ static std::shared_ptr<PropertyContainer> GetPropertyContainer(
Handle handle{propertyHandle};
if (handle.IsType(Handle::kProperty)) {
int i = handle.GetParentIndex();
auto data = Sources::GetInstance().Get(Handle{i, Handle::kSource});
auto data = Instance::GetInstance().GetSource(Handle{i, Handle::kSource});
if (!data) {
*status = CS_INVALID_HANDLE;
return nullptr;
@@ -41,7 +37,7 @@ static std::shared_ptr<PropertyContainer> GetPropertyContainer(
container = data->source;
} else if (handle.IsType(Handle::kSinkProperty)) {
int i = handle.GetParentIndex();
auto data = Sinks::GetInstance().Get(Handle{i, Handle::kSink});
auto data = Instance::GetInstance().GetSink(Handle{i, Handle::kSink});
if (!data) {
*status = CS_INVALID_HANDLE;
return nullptr;
@@ -165,7 +161,7 @@ std::vector<std::string> GetEnumPropertyChoices(CS_Property property,
//
CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SOURCE_UNKNOWN;
@@ -174,7 +170,7 @@ CS_SourceKind GetSourceKind(CS_Source source, CS_Status* status) {
}
std::string GetSourceName(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -184,7 +180,7 @@ std::string GetSourceName(CS_Source source, CS_Status* status) {
wpi::StringRef GetSourceName(CS_Source source, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::StringRef{};
@@ -193,7 +189,7 @@ wpi::StringRef GetSourceName(CS_Source source, wpi::SmallVectorImpl<char>& buf,
}
std::string GetSourceDescription(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -205,7 +201,7 @@ std::string GetSourceDescription(CS_Source source, CS_Status* status) {
wpi::StringRef GetSourceDescription(CS_Source source,
wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::StringRef{};
@@ -214,7 +210,7 @@ wpi::StringRef GetSourceDescription(CS_Source source,
}
uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -225,7 +221,7 @@ uint64_t GetSourceLastFrameTime(CS_Source source, CS_Status* status) {
void SetSourceConnectionStrategy(CS_Source source,
CS_ConnectionStrategy strategy,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -234,7 +230,7 @@ void SetSourceConnectionStrategy(CS_Source source,
}
bool IsSourceConnected(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -243,7 +239,7 @@ bool IsSourceConnected(CS_Source source, CS_Status* status) {
}
bool IsSourceEnabled(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -253,7 +249,7 @@ bool IsSourceEnabled(CS_Source source, CS_Status* status) {
CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -269,7 +265,7 @@ CS_Property GetSourceProperty(CS_Source source, const wpi::Twine& name,
wpi::ArrayRef<CS_Property> EnumerateSourceProperties(
CS_Source source, wpi::SmallVectorImpl<CS_Property>& vec,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -282,7 +278,7 @@ wpi::ArrayRef<CS_Property> EnumerateSourceProperties(
}
VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return VideoMode{};
@@ -292,7 +288,7 @@ VideoMode GetSourceVideoMode(CS_Source source, CS_Status* status) {
bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -302,7 +298,7 @@ bool SetSourceVideoMode(CS_Source source, const VideoMode& mode,
bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -312,7 +308,7 @@ bool SetSourcePixelFormat(CS_Source source, VideoMode::PixelFormat pixelFormat,
bool SetSourceResolution(CS_Source source, int width, int height,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -321,7 +317,7 @@ bool SetSourceResolution(CS_Source source, int width, int height,
}
bool SetSourceFPS(CS_Source source, int fps, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
@@ -329,9 +325,47 @@ 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 = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::vector<VideoMode>{};
@@ -342,21 +376,18 @@ std::vector<VideoMode> EnumerateSourceVideoModes(CS_Source source,
wpi::ArrayRef<CS_Sink> EnumerateSourceSinks(CS_Source source,
wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto& inst = Instance::GetInstance();
auto data = inst.GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::ArrayRef<CS_Sink>{};
}
vec.clear();
Sinks::GetInstance().ForEach([&](CS_Sink sinkHandle, const SinkData& data) {
if (source == data.sourceHandle.load()) vec.push_back(sinkHandle);
});
return vec;
return inst.EnumerateSourceSinks(source, vec);
}
CS_Source CopySource(CS_Source source, CS_Status* status) {
if (source == 0) return 0;
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -367,17 +398,13 @@ CS_Source CopySource(CS_Source source, CS_Status* status) {
void ReleaseSource(CS_Source source, CS_Status* status) {
if (source == 0) return;
auto& inst = Sources::GetInstance();
auto data = inst.Get(source);
auto& inst = Instance::GetInstance();
auto data = inst.GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
if (data->refCount-- == 0) {
Notifier::GetInstance().NotifySource(data->source->GetName(), source,
CS_SOURCE_DESTROYED);
inst.Free(source);
}
if (data->refCount-- == 0) inst.DestroySource(source);
}
//
@@ -385,7 +412,7 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
//
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -394,7 +421,7 @@ void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
}
int GetCameraBrightness(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -403,7 +430,7 @@ int GetCameraBrightness(CS_Source source, CS_Status* status) {
}
void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -412,7 +439,7 @@ void SetCameraWhiteBalanceAuto(CS_Source source, CS_Status* status) {
}
void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -422,7 +449,7 @@ void SetCameraWhiteBalanceHoldCurrent(CS_Source source, CS_Status* status) {
void SetCameraWhiteBalanceManual(CS_Source source, int value,
CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -431,7 +458,7 @@ void SetCameraWhiteBalanceManual(CS_Source source, int value,
}
void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -440,7 +467,7 @@ void SetCameraExposureAuto(CS_Source source, CS_Status* status) {
}
void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -449,7 +476,7 @@ void SetCameraExposureHoldCurrent(CS_Source source, CS_Status* status) {
}
void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -462,7 +489,7 @@ void SetCameraExposureManual(CS_Source source, int value, CS_Status* status) {
//
CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return CS_SINK_UNKNOWN;
@@ -471,7 +498,7 @@ CS_SinkKind GetSinkKind(CS_Sink sink, CS_Status* status) {
}
std::string GetSinkName(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -481,7 +508,7 @@ std::string GetSinkName(CS_Sink sink, CS_Status* status) {
wpi::StringRef GetSinkName(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::StringRef{};
@@ -490,7 +517,7 @@ wpi::StringRef GetSinkName(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
}
std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -501,7 +528,7 @@ std::string GetSinkDescription(CS_Sink sink, CS_Status* status) {
wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::StringRef{};
@@ -511,7 +538,7 @@ wpi::StringRef GetSinkDescription(CS_Sink sink, wpi::SmallVectorImpl<char>& buf,
CS_Property GetSinkProperty(CS_Sink sink, const wpi::Twine& name,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -526,7 +553,7 @@ CS_Property GetSinkProperty(CS_Sink sink, const wpi::Twine& name,
wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
CS_Sink sink, wpi::SmallVectorImpl<CS_Property>& vec, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -537,8 +564,45 @@ 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 = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
@@ -546,7 +610,7 @@ void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
if (source == 0) {
data->sink->SetSource(nullptr);
} else {
auto sourceData = Sources::GetInstance().Get(source);
auto sourceData = Instance::GetInstance().GetSource(source);
if (!sourceData) {
*status = CS_INVALID_HANDLE;
return;
@@ -554,12 +618,12 @@ void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
data->sink->SetSource(sourceData->source);
}
data->sourceHandle.store(source);
Notifier::GetInstance().NotifySinkSourceChanged(data->sink->GetName(), sink,
source);
Instance::GetInstance().notifier.NotifySinkSourceChanged(
data->sink->GetName(), sink, source);
}
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -569,7 +633,7 @@ CS_Source GetSinkSource(CS_Sink sink, CS_Status* status) {
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
CS_Status* status) {
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -579,7 +643,7 @@ CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
if (sink == 0) return 0;
auto data = Sinks::GetInstance().Get(sink);
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return 0;
@@ -590,17 +654,13 @@ CS_Sink CopySink(CS_Sink sink, CS_Status* status) {
void ReleaseSink(CS_Sink sink, CS_Status* status) {
if (sink == 0) return;
auto& inst = Sinks::GetInstance();
auto data = inst.Get(sink);
auto& inst = Instance::GetInstance();
auto data = inst.GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return;
}
if (data->refCount-- == 0) {
Notifier::GetInstance().NotifySink(data->sink->GetName(), sink,
CS_SINK_DESTROYED);
inst.Free(sink);
}
if (data->refCount-- == 0) inst.DestroySink(sink);
}
//
@@ -608,22 +668,22 @@ void ReleaseSink(CS_Sink sink, CS_Status* status) {
//
void SetListenerOnStart(std::function<void()> onStart) {
Notifier::GetInstance().SetOnStart(onStart);
Instance::GetInstance().notifier.SetOnStart(onStart);
}
void SetListenerOnExit(std::function<void()> onExit) {
Notifier::GetInstance().SetOnExit(onExit);
Instance::GetInstance().notifier.SetOnExit(onExit);
}
CS_Listener AddListener(std::function<void(const RawEvent& event)> callback,
int eventMask, bool immediateNotify,
CS_Status* status) {
int uid = Notifier::GetInstance().AddListener(callback, eventMask);
auto& inst = Instance::GetInstance();
int uid = inst.notifier.AddListener(callback, eventMask);
if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) {
// start network interface event listener
NetworkListener::GetInstance().Start();
if (immediateNotify)
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
inst.networkListener.Start();
if (immediateNotify) inst.notifier.NotifyNetworkInterfacesChanged();
}
if (immediateNotify) {
// TODO
@@ -637,7 +697,7 @@ void RemoveListener(CS_Listener handle, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return;
}
Notifier::GetInstance().RemoveListener(uid);
Instance::GetInstance().notifier.RemoveListener(uid);
}
bool NotifierDestroyed() { return Notifier::destroyed(); }
@@ -646,86 +706,60 @@ bool NotifierDestroyed() { return Notifier::destroyed(); }
// Telemetry Functions
//
void SetTelemetryPeriod(double seconds) {
Telemetry::GetInstance().Start();
Telemetry::GetInstance().SetPeriod(seconds);
auto& inst = Instance::GetInstance();
inst.telemetry.Start();
inst.telemetry.SetPeriod(seconds);
}
double GetTelemetryElapsedTime() {
return Telemetry::GetInstance().GetElapsedTime();
return Instance::GetInstance().telemetry.GetElapsedTime();
}
int64_t GetTelemetryValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return Telemetry::GetInstance().GetValue(handle, kind, status);
return Instance::GetInstance().telemetry.GetValue(handle, kind, status);
}
double GetTelemetryAverageValue(CS_Handle handle, CS_TelemetryKind kind,
CS_Status* status) {
return Telemetry::GetInstance().GetAverageValue(handle, kind, status);
return Instance::GetInstance().telemetry.GetAverageValue(handle, kind,
status);
}
//
// Logging Functions
//
void SetLogger(LogFunc func, unsigned int min_level) {
Logger& logger = Logger::GetInstance();
auto& logger = Instance::GetInstance().logger;
logger.SetLogger(func);
logger.set_min_level(min_level);
}
void SetDefaultLogger(unsigned int min_level) {
Logger& logger = Logger::GetInstance();
logger.SetDefaultLogger();
logger.set_min_level(min_level);
auto& inst = Instance::GetInstance();
inst.SetDefaultLogger();
inst.logger.set_min_level(min_level);
}
//
// Shutdown Function
//
void Shutdown() { Instance::GetInstance().Shutdown(); }
//
// Utility Functions
//
wpi::ArrayRef<CS_Source> EnumerateSourceHandles(
wpi::SmallVectorImpl<CS_Source>& vec, CS_Status* status) {
return Sources::GetInstance().GetAll(vec);
return Instance::GetInstance().EnumerateSourceHandles(vec);
}
wpi::ArrayRef<CS_Sink> EnumerateSinkHandles(wpi::SmallVectorImpl<CS_Sink>& vec,
CS_Status* status) {
return Sinks::GetInstance().GetAll(vec);
return Instance::GetInstance().EnumerateSinkHandles(vec);
}
std::string GetHostname() {
#ifdef __linux__
char name[256];
if (::gethostname(name, sizeof(name)) != 0) return "";
name[255] = '\0'; // Per POSIX, may not be null terminated if too long
return name;
#else
return ""; // TODO
#endif
}
std::vector<std::string> GetNetworkInterfaces() {
#ifdef __linux__
struct ifaddrs* ifa;
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
std::vector<std::string> rv;
char buf[256];
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
if (!i->ifa_addr) continue; // no address
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
const char* addr =
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
if (!addr) continue; // error converting address
rv.emplace_back(addr);
}
::freeifaddrs(ifa);
return rv;
#else
return std::vector<std::string>{}; // TODO
#endif
}
std::string GetHostname() { return wpi::GetHostname(); }
} // namespace cs

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

@@ -87,6 +87,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
cs::Shutdown();
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return;
@@ -181,16 +183,17 @@ static inline bool CheckStatus(JNIEnv* env, CS_Status status) {
return status == CS_OK;
}
#ifdef __linux__
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());
}
#endif
static jobject MakeJObject(JNIEnv* env, const cs::VideoMode& videoMode) {
static jmethodID constructor =
@@ -406,10 +409,6 @@ JNIEXPORT jint JNICALL
Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraDev
(JNIEnv* env, jclass, jstring name, jint dev)
{
#ifndef __linux__
unsupportedEx.Throw(env, "USB is not supported yet");
return 0;
#else
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
@@ -418,7 +417,6 @@ Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraDev
auto val = cs::CreateUsbCameraDev(JStringRef{env, name}.str(), dev, &status);
CheckStatus(env, status);
return val;
#endif
}
/*
@@ -430,10 +428,6 @@ JNIEXPORT jint JNICALL
Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraPath
(JNIEnv* env, jclass, jstring name, jstring path)
{
#ifndef __linux__
unsupportedEx.Throw(env, "USB is not supported yet");
return 0;
#else
if (!name) {
nullPointerEx.Throw(env, "name cannot be null");
return 0;
@@ -447,7 +441,6 @@ Java_edu_wpi_cscore_CameraServerJNI_createUsbCameraPath
JStringRef{env, path}.str(), &status);
CheckStatus(env, status);
return val;
#endif
}
/*
@@ -761,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
@@ -949,15 +972,25 @@ JNIEXPORT jstring JNICALL
Java_edu_wpi_cscore_CameraServerJNI_getUsbCameraPath
(JNIEnv* env, jclass, jint source)
{
#ifndef __linux__
unsupportedEx.Throw(env, "USB is not supported yet");
return 0;
#else
CS_Status status = 0;
auto str = cs::GetUsbCameraPath(source, &status);
if (!CheckStatus(env, status)) return nullptr;
return MakeJString(env, str);
#endif
}
/*
* 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);
}
/*
@@ -1259,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
@@ -1582,10 +1645,6 @@ JNIEXPORT jobjectArray JNICALL
Java_edu_wpi_cscore_CameraServerJNI_enumerateUsbCameras
(JNIEnv* env, jclass)
{
#ifndef __linux__
unsupportedEx.Throw(env, "USB is not supported yet");
return 0;
#else
CS_Status status = 0;
auto arr = cs::EnumerateUsbCameras(&status);
if (!CheckStatus(env, status)) return nullptr;
@@ -1597,7 +1656,6 @@ Java_edu_wpi_cscore_CameraServerJNI_enumerateUsbCameras
env->SetObjectArrayElement(jarr, i, jelem);
}
return jarr;
#endif
}
/*

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);
@@ -440,20 +459,18 @@ void CS_SetLogger(CS_LogFunc func, unsigned int min_level);
void CS_SetDefaultLogger(unsigned int min_level);
/** @} */
/**
* @defgroup cscore_shutdown_cfunc Library Shutdown Function
* @{
*/
void CS_Shutdown(void);
/** @} */
/**
* @defgroup cscore_utility_cfunc Utility Functions
* @{
*/
/**
* 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);
@@ -465,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);
@@ -377,6 +402,13 @@ void SetLogger(LogFunc func, unsigned int min_level);
void SetDefaultLogger(unsigned int min_level);
/** @} */
/**
* @defgroup cscore_shutdown_func Library Shutdown Function
* @{
*/
void Shutdown();
/** @} */
/**
* @defgroup cscore_utility_func Utility Functions
* @{

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

@@ -7,7 +7,6 @@
#include "NetworkListener.h"
#ifdef __linux__
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/eventfd.h>
@@ -18,42 +17,55 @@
#include <unistd.h>
#include <cerrno>
#endif
#include <wpi/SafeThread.h>
#include "Log.h"
#include "Notifier.h"
using namespace cs;
class NetworkListener::Thread : public wpi::SafeThread {
class NetworkListener::Impl {
public:
void Main();
Impl(wpi::Logger& logger, Notifier& notifier)
: m_logger(logger), m_notifier(notifier) {}
#ifdef __linux__
int m_command_fd = -1;
#endif
wpi::Logger& m_logger;
Notifier& m_notifier;
class Thread : public wpi::SafeThread {
public:
Thread(wpi::Logger& logger, Notifier& notifier)
: m_logger(logger), m_notifier(notifier) {}
void Main();
wpi::Logger& m_logger;
Notifier& m_notifier;
int m_command_fd = -1;
};
wpi::SafeThreadOwner<Thread> m_owner;
};
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier)
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
NetworkListener::~NetworkListener() { Stop(); }
void NetworkListener::Start() {
auto thr = m_owner.GetThread();
if (!thr) m_owner.Start();
m_impl->m_owner.Start(m_impl->m_logger, m_impl->m_notifier);
}
void NetworkListener::Stop() {
// Wake up thread
if (auto thr = m_owner.GetThread()) {
if (auto thr = m_impl->m_owner.GetThread()) {
thr->m_active = false;
#ifdef __linux__
if (thr->m_command_fd >= 0) eventfd_write(thr->m_command_fd, 1);
#endif
}
m_owner.Stop();
m_impl->m_owner.Stop();
}
void NetworkListener::Thread::Main() {
#ifdef __linux__
void NetworkListener::Impl::Thread::Main() {
// Create event socket so we can be shut down
m_command_fd = ::eventfd(0, 0);
if (m_command_fd < 0) {
@@ -125,12 +137,11 @@ void NetworkListener::Thread::Main() {
if (nh->nlmsg_type == NLMSG_DONE) break;
if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK ||
nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) {
Notifier::GetInstance().NotifyNetworkInterfacesChanged();
m_notifier.NotifyNetworkInterfacesChanged();
}
}
}
::close(sd);
::close(m_command_fd);
m_command_fd = -1;
#endif
}

View File

@@ -0,0 +1,37 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_cpp.h" // NOLINT(build/include_order)
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/types.h>
#include <unistd.h>
namespace cs {
std::vector<std::string> GetNetworkInterfaces() {
struct ifaddrs* ifa;
if (::getifaddrs(&ifa) != 0) return std::vector<std::string>{};
std::vector<std::string> rv;
char buf[256];
for (struct ifaddrs* i = ifa; i; i = i->ifa_next) {
if (!i->ifa_addr) continue; // no address
if (i->ifa_addr->sa_family != AF_INET) continue; // only return IPv4
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(i->ifa_addr);
const char* addr =
::inet_ntop(addr_in->sin_family, &addr_in->sin_addr, buf, sizeof(buf));
if (!addr) continue; // error converting address
rv.emplace_back(addr);
}
::freeifaddrs(ifa);
return rv;
}
} // namespace cs

View File

@@ -8,9 +8,7 @@
#ifndef CSCORE_USBCAMERABUFFER_H_
#define CSCORE_USBCAMERABUFFER_H_
#ifdef __linux__
#include <sys/mman.h>
#endif
#include <utility>
@@ -29,7 +27,6 @@ class UsbCameraBuffer {
UsbCameraBuffer(const UsbCameraBuffer&) = delete;
UsbCameraBuffer& operator=(const UsbCameraBuffer&) = delete;
#ifdef __linux__
UsbCameraBuffer(int fd, size_t length, off_t offset) noexcept
: m_length{length} {
m_data =
@@ -43,7 +40,6 @@ class UsbCameraBuffer {
~UsbCameraBuffer() {
if (m_data) munmap(m_data, m_length);
}
#endif
friend void swap(UsbCameraBuffer& first, UsbCameraBuffer& second) noexcept {
using std::swap;

View File

@@ -7,7 +7,6 @@
#include "UsbCameraImpl.h"
#ifdef __linux__
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
@@ -22,29 +21,27 @@
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#elif defined(_WIN32)
#endif
#include <algorithm>
#include <wpi/FileSystem.h>
#include <wpi/Path.h>
#include <wpi/SmallString.h>
#include <wpi/memory.h>
#include <wpi/raw_ostream.h>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Instance.h"
#include "JpegUtil.h"
#include "Log.h"
#include "Notifier.h"
#include "Telemetry.h"
#include "UsbUtil.h"
#include "c_util.h"
#include "cscore_cpp.h"
using namespace cs;
#ifdef __linux__
static constexpr char const* kPropWbAuto = "white_balance_temperature_auto";
static constexpr char const* kPropWbValue = "white_balance_temperature";
static constexpr char const* kPropExAuto = "exposure_auto";
@@ -215,14 +212,17 @@ static std::string GetDescriptionImpl(const char* cpath) {
return std::string{};
}
UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, const wpi::Twine& path)
: SourceImpl{name},
UsbCameraImpl::UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger,
Notifier& notifier, Telemetry& telemetry,
const wpi::Twine& path)
: SourceImpl{name, logger, notifier, telemetry},
m_path{path.str()},
m_fd{-1},
m_command_fd{eventfd(0, 0)},
m_active{true} {
SetDescription(GetDescriptionImpl(m_path.c_str()));
SetQuirks();
CreateProperty(kPropConnectVerbose, [] {
return std::make_unique<UsbCameraProperty>(kPropConnectVerbose,
kPropConnectVerboseId,
@@ -646,7 +646,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
DeviceConnect();
}
if (wasStreaming) DeviceStreamOn();
Notifier::GetInstance().NotifySourceVideoMode(*this, newMode);
m_notifier.NotifySourceVideoMode(*this, newMode);
lock.lock();
} else if (newMode.fps != m_mode.fps) {
m_mode = newMode;
@@ -656,7 +656,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetMode(
if (wasStreaming) DeviceStreamOff();
DeviceSetFPS();
if (wasStreaming) DeviceStreamOn();
Notifier::GetInstance().NotifySourceVideoMode(*this, newMode);
m_notifier.NotifySourceVideoMode(*this, newMode);
lock.lock();
}
@@ -892,7 +892,7 @@ void UsbCameraImpl::DeviceCacheMode() {
if (formatChanged) DeviceSetMode();
if (fpsChanged) DeviceSetFPS();
Notifier::GetInstance().NotifySourceVideoMode(*this, m_mode);
m_notifier.NotifySourceVideoMode(*this, m_mode);
}
void UsbCameraImpl::DeviceCacheProperty(
@@ -1074,7 +1074,7 @@ void UsbCameraImpl::DeviceCacheVideoModes() {
std::lock_guard<wpi::mutex> lock(m_mutex);
m_videoModes.swap(modes);
}
Notifier::GetInstance().NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
m_notifier.NotifySource(*this, CS_SOURCE_VIDEOMODES_UPDATED);
}
CS_StatusValue UsbCameraImpl::SendAndWait(Message&& msg) const {
@@ -1265,17 +1265,14 @@ CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
CS_Status* status) {
auto source = std::make_shared<UsbCameraImpl>(name, path);
auto handle = Sources::GetInstance().Allocate(CS_SOURCE_USB, source);
Notifier::GetInstance().NotifySource(name, handle, CS_SOURCE_CREATED);
// Start thread after the source created event to ensure other events
// come after it.
source->Start();
return handle;
auto& inst = Instance::GetInstance();
return inst.CreateSource(CS_SOURCE_USB, std::make_shared<UsbCameraImpl>(
name, inst.logger, inst.notifier,
inst.telemetry, path));
}
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
auto data = Sources::GetInstance().Get(source);
auto data = Instance::GetInstance().GetSource(source);
if (!data || data->kind != CS_SOURCE_USB) {
*status = CS_INVALID_HANDLE;
return std::string{};
@@ -1283,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();
@@ -1301,92 +1360,49 @@ 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 = ;
ERROR("Could not open /dev");
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;
}
} // namespace cs
extern "C" {
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
return cs::CreateUsbCameraDev(name, dev, status);
}
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Status* status) {
return cs::CreateUsbCameraPath(name, path, status);
}
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
return ConvertToC(cs::GetUsbCameraPath(source, status));
}
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);
}
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);
}
std::free(cameras);
}
} // extern "C"
#else
extern "C" {
CS_Source CS_CreateUsbCameraDev(const char* name, int dev, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return 0;
}
CS_Source CS_CreateUsbCameraPath(const char* name, const char* path,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
return 0;
}
char* CS_GetUsbCameraPath(CS_Source source, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return nullptr;
}
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status) {
*status = CS_INVALID_HANDLE;
return nullptr;
}
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count) {}
} // extern "C"
#endif // __linux__

View File

@@ -8,9 +8,7 @@
#ifndef CSCORE_USBCAMERAIMPL_H_
#define CSCORE_USBCAMERAIMPL_H_
#ifdef __linux__
#include <linux/videodev2.h>
#endif
#include <atomic>
#include <memory>
@@ -33,12 +31,16 @@
namespace cs {
class Notifier;
class Telemetry;
class UsbCameraImpl : public SourceImpl {
public:
UsbCameraImpl(const wpi::Twine& name, const wpi::Twine& path);
UsbCameraImpl(const wpi::Twine& name, wpi::Logger& logger, Notifier& notifier,
Telemetry& telemetry, const wpi::Twine& path);
~UsbCameraImpl() override;
void Start();
void Start() override;
// Property functions
void SetProperty(int property, int value, CS_Status* status) override;
@@ -145,24 +147,18 @@ class UsbCameraImpl : public SourceImpl {
bool m_modeSetResolution{false};
bool m_modeSetFPS{false};
int m_connectVerbose{1};
#ifdef __linux__
unsigned m_capabilities = 0;
#endif
// Number of buffers to ask OS for
static constexpr int kNumBuffers = 4;
#ifdef __linux__
std::array<UsbCameraBuffer, kNumBuffers> m_buffers;
#endif
//
// Path never changes, so not protected by mutex.
//
std::string m_path;
#ifdef __linux__
std::atomic_int m_fd;
std::atomic_int m_command_fd; // for command eventfd
#endif
std::atomic_bool m_active; // set to false to terminate thread
std::thread m_cameraThread;

View File

@@ -9,13 +9,12 @@
#include <wpi/STLExtras.h>
#include <wpi/SmallString.h>
#include <wpi/raw_ostream.h>
#include "UsbUtil.h"
using namespace cs;
#ifdef __linux__
static int GetIntCtrlIoctl(int fd, unsigned id, int type, int64_t* value) {
unsigned ctrl_class = V4L2_CTRL_ID2CLASS(id);
if (type == V4L2_CTRL_TYPE_INTEGER64 || V4L2_CTRL_DRIVER_PRIV(id) ||
@@ -153,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;
@@ -245,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);
}
}
}
@@ -322,5 +329,3 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd,
return rv >= 0;
}
#endif // __linux__

View File

@@ -8,9 +8,7 @@
#ifndef CSCORE_USBCAMERAPROPERTY_H_
#define CSCORE_USBCAMERAPROPERTY_H_
#ifdef __linux__
#include <linux/videodev2.h>
#endif
#include <memory>
@@ -50,7 +48,6 @@ class UsbCameraProperty : public PropertyImpl {
maximum = 100;
}
#ifdef __linux__
#ifdef VIDIOC_QUERY_EXT_CTRL
explicit UsbCameraProperty(const struct v4l2_query_ext_ctrl& ctrl);
#endif
@@ -62,7 +59,6 @@ class UsbCameraProperty : public PropertyImpl {
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, int fd, int newValue,
const wpi::Twine& newValueStr) const;
#endif
// If this is a device (rather than software) property
bool device{true};
@@ -75,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

@@ -8,23 +8,19 @@
#include "UsbUtil.h"
#include <fcntl.h>
#ifdef __linux__
#include <libgen.h>
#include <sys/ioctl.h>
#endif
#include <wpi/Format.h>
#include <wpi/SmallString.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include "Instance.h"
#include "Log.h"
namespace cs {
#ifdef __linux__
static wpi::StringRef GetUsbNameFromFile(int vendor, int product,
wpi::SmallVectorImpl<char>& buf) {
int fd = open("/var/lib/usbutils/usb.ids", O_RDONLY);
@@ -155,12 +151,11 @@ int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
if (!quiet && retval < 0) {
wpi::SmallString<64> localfile{file};
localfile.push_back('\0');
ERROR("ioctl " << name << " failed at " << basename(localfile.data()) << ":"
<< line << ": " << std::strerror(errno));
WPI_ERROR(Instance::GetInstance().logger,
"ioctl " << name << " failed at " << basename(localfile.data())
<< ":" << line << ": " << std::strerror(errno));
}
return retval;
}
#endif // __linux__
} // namespace cs

View File

@@ -15,8 +15,6 @@
namespace cs {
#ifdef __linux__
wpi::StringRef GetUsbNameFromId(int vendor, int product,
wpi::SmallVectorImpl<char>& buf);
@@ -28,8 +26,6 @@ int CheckedIoctl(int fd, unsigned long req, void* data, // NOLINT(runtime/int)
#define TryIoctl(fd, req, data) \
CheckedIoctl(fd, req, data, #req, __FILE__, __LINE__, true)
#endif // __linux__
} // namespace cs
#endif // CSCORE_USBUTIL_H_

View File

@@ -0,0 +1,20 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-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 "NetworkListener.h"
using namespace cs;
class NetworkListener::Impl {};
NetworkListener::NetworkListener(wpi::Logger& logger, Notifier& notifier) {}
NetworkListener::~NetworkListener() {}
void NetworkListener::Start() {}
void NetworkListener::Stop() {}

View File

@@ -5,16 +5,12 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include "cscore_cpp.h"
// clang-format off
#ifdef _MSC_VER
#pragma message "warning: llvm/None.h is deprecated; include wpi/None.h instead"
#else
#warning "llvm/None.h is deprecated; include wpi/None.h instead"
#endif
// clang-format on
namespace cs {
#include "wpi/None.h"
std::vector<std::string> GetNetworkInterfaces() {
return std::vector<std::string>{}; // TODO
}
namespace llvm = wpi;
} // namespace cs

View File

@@ -0,0 +1,39 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#include "cscore_cpp.h"
namespace cs {
CS_Source CreateUsbCameraDev(const wpi::Twine& name, int dev,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
return 0;
}
CS_Source CreateUsbCameraPath(const wpi::Twine& name, const wpi::Twine& path,
CS_Status* status) {
*status = CS_INVALID_HANDLE;
return 0;
}
std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
*status = CS_INVALID_HANDLE;
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>{};
}
} // namespace cs

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

@@ -0,0 +1,61 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2015-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 "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 {
public:
Impl(wpi::Logger& logger, Notifier& notifier)
: m_logger(logger), m_notifier(notifier) {}
wpi::Logger& m_logger;
Notifier& m_notifier;
HANDLE eventHandle = 0;
};
// 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(wpi::Logger& logger, Notifier& notifier)
: m_impl(std::make_unique<Impl>(logger, notifier)) {}
NetworkListener::~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

@@ -0,0 +1,42 @@
/*----------------------------------------------------------------------------*/
/* 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. */
/*----------------------------------------------------------------------------*/
#include <uv.h>
#include "cscore_cpp.h"
#pragma comment(lib, "Ws2_32.lib")
namespace cs {
std::vector<std::string> GetNetworkInterfaces() {
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')
@@ -100,6 +100,7 @@ task generateJavaDocs(type: Javadoc) {
options.links("https://docs.oracle.com/javase/8/docs/api/")
options.addStringOption "tag", "pre:a:Pre-Condition"
options.addStringOption('Xdoclint:accessibility,html,missing,reference,syntax')
options.addBooleanOption('html5', true)
dependsOn project(':wpilibj').generateJavaVersion
dependsOn project(':hal').generateUsageReporting
source project(':hal').sourceSets.main.java
@@ -111,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

@@ -22,6 +22,47 @@ endforeach()
string(REPLACE ";" "\n" usage_reporting_types_cpp "${usage_reporting_types_cpp}")
string(REPLACE ";" "\n" usage_reporting_instances_cpp "${usage_reporting_instances_cpp}")
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)
add_library(hal ${hal_shared_native_src})
set_target_properties(hal PROPERTIES DEBUG_POSTFIX "d")
if(USE_EXTERNAL_HAL)
include(${EXTERNAL_HAL_FILE})
else()
target_sources(hal PRIVATE ${hal_sim_native_src} ${hal_sim_jni_src})
endif()
configure_file(src/generate/FRCUsageReporting.h.in gen/hal/FRCUsageReporting.h)
set_target_properties(hal PROPERTIES OUTPUT_NAME "wpiHal")
target_include_directories(hal PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/hal>)
target_include_directories(hal PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/gen>
$<INSTALL_INTERFACE:${include_dest}/hal>)
target_link_libraries(hal PUBLIC wpiutil)
set_property(TARGET hal PROPERTY FOLDER "libraries")
install(TARGETS hal EXPORT hal DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/hal")
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/gen DESTINATION "${include_dest}/hal")
if (MSVC)
set (hal_config_dir ${wpilib_dest})
else()
set (hal_config_dir share/hal)
endif()
install(FILES hal-config.cmake DESTINATION ${hal_config_dir})
install(EXPORT hal DESTINATION ${hal_config_dir})
# Java bindings
if (NOT WITHOUT_JAVA)
@@ -53,59 +94,32 @@ if (NOT WITHOUT_JAVA)
set_property(TARGET hal_jar PROPERTY FOLDER "java")
endif()
add_library(haljni ${hal_shared_jni_src})
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)
add_library(hal ${hal_shared_native_src} ${hal_shared_jni_src})
if(USE_EXTERNAL_HAL)
include(${EXTERNAL_HAL_FILE})
else()
target_sources(hal PRIVATE ${hal_sim_native_src} ${hal_sim_jni_src})
endif()
configure_file(src/generate/FRCUsageReporting.h.in gen/hal/FRCUsageReporting.h)
set_target_properties(hal PROPERTIES OUTPUT_NAME "wpiHal")
target_include_directories(hal PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<INSTALL_INTERFACE:${include_dest}/hal>)
target_include_directories(hal PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/gen>
$<INSTALL_INTERFACE:${include_dest}/hal>)
target_link_libraries(hal PUBLIC wpiutil)
set_property(TARGET hal PROPERTY FOLDER "libraries")
if (NOT WITHOUT_JAVA)
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
target_include_directories(hal PRIVATE ${JNI_INCLUDE_DIRS})
target_include_directories(hal PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
if(USE_EXTERNAL_HAL)
include(${EXTERNAL_HAL_FILE})
else()
target_link_libraries(hal PRIVATE hal_jni_headers)
target_sources(haljni PRIVATE ${hal_sim_jni_src})
endif()
add_dependencies(hal hal_jar)
set_target_properties(haljni PROPERTIES OUTPUT_NAME "wpiHaljni")
target_link_libraries(haljni PUBLIC hal wpiutil)
set_property(TARGET haljni PROPERTY FOLDER "libraries")
if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
target_include_directories(haljni PRIVATE ${JNI_INCLUDE_DIRS})
target_include_directories(haljni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
else()
target_link_libraries(haljni PRIVATE hal_jni_headers)
endif()
add_dependencies(haljni hal_jar)
if (MSVC)
install(TARGETS haljni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
install(TARGETS haljni EXPORT haljni DESTINATION "${main_lib_dest}")
endif()
install(TARGETS hal EXPORT hal DESTINATION "${main_lib_dest}")
install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/hal")
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/gen DESTINATION "${include_dest}/hal")
if (NOT WITHOUT_JAVA AND MSVC)
install(TARGETS hal RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
endif()
if (MSVC)
set (hal_config_dir ${wpilib_dest})
else()
set (hal_config_dir share/hal)
endif()
install(FILES hal-config.cmake DESTINATION ${hal_config_dir})
install(EXPORT hal DESTINATION ${hal_config_dir})

View File

@@ -51,11 +51,50 @@ ext {
binary.lib project: ':hal', library: 'hal', linkage: shared
}
addHalJniDependency = { binary->
binary.tasks.withType(AbstractNativeSourceCompileTask) {
it.dependsOn generateUsageReporting
}
binary.lib project: ':hal', library: 'halJNIShared', linkage: 'shared'
}
nativeName = 'hal'
setBaseName = 'wpiHal'
devMain = 'DevMain'
niLibraries = true
generatedHeaders = "$buildDir/generated/headers"
jniSplitSetup = {
it.tasks.withType(AbstractNativeSourceCompileTask) {
it.dependsOn generateUsageReporting
}
if (it.targetPlatform.architecture.name == 'athena') {
it.sources {
athenaJniCpp(CppSourceSet) {
source {
srcDirs = ["${rootDir}/shared/singlelib", "$buildDir/generated/cpp"]
include '**/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
srcDir generatedHeaders
}
}
}
} else {
it.sources {
simJniCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/sim'
include '**/jni/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
srcDir generatedHeaders
}
}
}
}
}
splitSetup = {
it.tasks.withType(AbstractNativeSourceCompileTask) {
it.dependsOn generateUsageReporting
@@ -64,8 +103,9 @@ ext {
it.sources {
athenaCpp(CppSourceSet) {
source {
srcDirs = ['src/main/native/athena', "$buildDir/generated/cpp"]
srcDirs = ['src/main/native/athena']
include '**/*.cpp'
exclude '**/jni/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
@@ -79,6 +119,7 @@ ext {
source {
srcDirs 'src/main/native/sim'
include '**/*.cpp'
exclude '**/jni/*.cpp'
}
exportedHeaders {
srcDir 'src/main/native/include'
@@ -142,7 +183,7 @@ model {
x86SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('HAL_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
retList << symbol
}
}
@@ -151,7 +192,7 @@ model {
x64SymbolFilter = { symbols ->
def retList = []
symbols.each { symbol ->
if (symbol.startsWith('HAL_') || symbol.startsWith('Java_') || symbol.startsWith('JNI_')) {
if (symbol.startsWith('HAL_') || symbol.startsWith('HALSIM_')) {
retList << symbol
}
}

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

@@ -21,7 +21,7 @@ public class JNIWrapper {
static {
if (!libraryLoaded) {
try {
loader = new RuntimeLoader<>("wpiHal", RuntimeLoader.getDefaultExtractionRoot(), JNIWrapper.class);
loader = new RuntimeLoader<>("wpiHaljni", RuntimeLoader.getDefaultExtractionRoot(), JNIWrapper.class);
loader.loadLibrary();
} catch (IOException ex) {
ex.printStackTrace();

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

@@ -9,6 +9,8 @@
#include <thread>
#include <wpi/raw_ostream.h>
#include "AnalogInternal.h"
#include "HALInitializer.h"
#include "hal/AnalogAccumulator.h"
@@ -169,6 +171,8 @@ void HAL_CalibrateAnalogGyro(HAL_GyroHandle handle, int32_t* status) {
HAL_InitAccumulator(gyro->handle, status);
if (*status != 0) return;
wpi::outs() << "Calibrating analog gyro for " << kCalibrationSampleTime
<< " seconds." << '\n';
Wait(kCalibrationSampleTime);
int64_t value;

View File

@@ -14,7 +14,6 @@
#include "HALInitializer.h"
#include "PortsInternal.h"
#include "hal/AnalogAccumulator.h"
#include "hal/HAL.h"
#include "hal/handles/HandlesInternal.h"
namespace hal {

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;

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