Compare commits

...

70 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
169 changed files with 3234 additions and 671 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

@@ -1,9 +1,9 @@
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2.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.6.1'
id 'edu.wpi.first.GradleVsCode' version '0.7.1'
id 'idea'
id 'visual-studio'
id 'com.gradle.build-scan' version '2.0.2'

View File

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

View File

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

View File

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

View File

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

@@ -112,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
@@ -146,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

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

View File

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

View File

@@ -111,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;
}
};
@@ -335,7 +335,8 @@ 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.
@@ -413,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>"
@@ -633,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()) {
@@ -655,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();
@@ -695,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"

View File

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

View File

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

View File

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

View File

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

View File

@@ -319,38 +319,8 @@ bool SourceImpl::SetConfigJson(const wpi::json& config, CS_Status* status) {
}
// properties
if (config.count("properties") != 0) {
for (auto&& prop : config.at("properties")) {
std::string name;
try {
name = prop.at("name").get<std::string>();
} catch (const wpi::json::exception& e) {
SWARNING("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>();
SINFO("SetConfigJson: setting property '" << name << "' to '" << val
<< '\'');
SetStringProperty(n, val, status);
} else if (v.is_boolean()) {
bool val = v.get<bool>();
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
SetProperty(n, val, status);
} else {
int val = v.get<int>();
SINFO("SetConfigJson: setting property '" << name << "' to " << val);
SetProperty(n, val, status);
}
} catch (const wpi::json::exception& e) {
SWARNING("SetConfigJson: could not read property value: " << e.what());
continue;
}
}
}
if (config.count("properties") != 0)
SetPropertiesJson(config.at("properties"), m_logger, GetName(), status);
return true;
}
@@ -401,28 +371,7 @@ wpi::json SourceImpl::GetConfigJsonObject(CS_Status* status) {
// TODO: output brightness, white balance, and exposure?
// properties
wpi::json props;
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;
}
props.emplace_back(prop);
}
wpi::json props = GetPropertiesJsonObject(status);
if (props.is_array()) j.emplace("properties", props);
return j;

View File

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

View File

@@ -277,6 +277,15 @@ CS_Property* CS_EnumerateSinkProperties(CS_Sink sink, int* count,
return out;
}
CS_Bool CS_SetSinkConfigJson(CS_Sink sink, const char* config,
CS_Status* status) {
return cs::SetSinkConfigJson(sink, config, status);
}
char* CS_GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
return cs::ConvertToC(cs::GetSinkConfigJson(sink, status));
}
void CS_SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
return cs::SetSinkSource(sink, source, status);
}

View File

@@ -564,6 +564,43 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
return vec;
}
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->sink->SetConfigJson(config, status);
}
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return false;
}
return data->sink->SetConfigJson(config, status);
}
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return std::string{};
}
return data->sink->GetConfigJson(status);
}
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {
*status = CS_INVALID_HANDLE;
return wpi::json{};
}
return data->sink->GetConfigJsonObject(status);
}
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status) {
auto data = Instance::GetInstance().GetSink(sink);
if (!data) {

View File

@@ -16,6 +16,11 @@ wpi::json VideoSource::GetConfigJsonObject() const {
return GetSourceConfigJsonObject(m_handle, &m_status);
}
wpi::json VideoSink::GetConfigJsonObject() const {
m_status = 0;
return GetSinkConfigJsonObject(m_handle, &m_status);
}
std::vector<VideoProperty> VideoSource::EnumerateProperties() const {
wpi::SmallVector<CS_Property, 32> handles_buf;
CS_Status status = 0;

View File

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

View File

@@ -223,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
* @{
@@ -322,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);
/** @} */
/**
@@ -381,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);
@@ -456,15 +471,6 @@ void CS_Shutdown(void);
* @{
*/
/**
* USB camera infomation
*/
typedef struct CS_UsbCameraInfo {
int dev;
char* path;
char* name;
} CS_UsbCameraInfo;
CS_UsbCameraInfo* CS_EnumerateUsbCameras(int* count, CS_Status* status);
void CS_FreeEnumeratedUsbCameras(CS_UsbCameraInfo* cameras, int count);
@@ -476,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

@@ -47,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;
};
/**
@@ -267,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);
/** @} */
/**
@@ -329,6 +332,11 @@ wpi::ArrayRef<CS_Property> EnumerateSinkProperties(
void SetSinkSource(CS_Sink sink, CS_Source source, CS_Status* status);
CS_Property GetSinkSourceProperty(CS_Sink sink, const wpi::Twine& name,
CS_Status* status);
bool SetSinkConfigJson(CS_Sink sink, wpi::StringRef config, CS_Status* status);
bool SetSinkConfigJson(CS_Sink sink, const wpi::json& config,
CS_Status* status);
std::string GetSinkConfigJson(CS_Sink sink, CS_Status* status);
wpi::json GetSinkConfigJsonObject(CS_Sink sink, CS_Status* status);
CS_Source GetSinkSource(CS_Sink sink, CS_Status* status);
CS_Sink CopySink(CS_Sink sink, CS_Status* status);
void ReleaseSink(CS_Sink sink, CS_Status* status);

View File

@@ -449,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.
*
@@ -802,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

@@ -260,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,
@@ -516,6 +521,21 @@ inline VideoProperty VideoSink::GetSourceProperty(const wpi::Twine& name) {
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
}
inline bool VideoSink::SetConfigJson(wpi::StringRef config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
m_status = 0;
return SetSinkConfigJson(m_handle, config, &m_status);
}
inline std::string VideoSink::GetConfigJson() const {
m_status = 0;
return GetSinkConfigJson(m_handle, &m_status);
}
inline MjpegServer::MjpegServer(const wpi::Twine& name,
const wpi::Twine& listenAddress, int port) {
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,27 +5,12 @@
/* the project. */
/*----------------------------------------------------------------------------*/
#include <winsock2.h> // NOLINT(build/include_order)
#include <iphlpapi.h> // NOLINT(build/include_order)
#include <ws2tcpip.h> // NOLINT(build/include_order)
#include <uv.h>
#include <iostream>
#include "cscore_cpp.h"
#pragma comment(lib, "IPHLPAPI.lib")
#pragma comment(lib, "Ws2_32.lib")
#define WORKING_BUFFER_SIZE 15000
#define MAX_TRIES 3
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
namespace cs {
std::vector<std::string> GetNetworkInterfaces() {
@@ -40,80 +25,18 @@ std::vector<std::string> GetNetworkInterfaces() {
for (int i = 0; i < counts; i++) {
if (adrs[i].is_internal) continue;
std::cout << adrs[i].name << std::endl;
InetNtop(PF_INET, &(adrs[i].netmask.netmask4.sin_addr.s_addr), ip,
sizeof(ip) - 1);
ip[49] = '\0';
std::cout << ip << std::endl;
InetNtop(PF_INET, &(adrs[i].address.address4.sin_addr.s_addr), ip,
sizeof(ip) - 1);
ip[49] = '\0';
std::cout << ip << std::endl;
addresses.emplace_back(std::string{ip});
}
uv_free_interface_addresses(adrs, counts);
std::cout << "finished\n";
return addresses;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
PIP_ADAPTER_ANYCAST_ADDRESS pAnycast = NULL;
PIP_ADAPTER_MULTICAST_ADDRESS pMulticast = NULL;
unsigned int i = 0;
ULONG outBufLen = 0;
ULONG Iterations = 0;
DWORD dwRetVal = 0;
// Allocate a 15 KB buffer to start with.
outBufLen = WORKING_BUFFER_SIZE;
do {
pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(MALLOC(outBufLen));
if (pAddresses == NULL) {
std::printf("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");
std::exit(1);
}
dwRetVal = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL,
pAddresses, &outBufLen);
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
FREE(pAddresses);
pAddresses = NULL;
} else {
break;
}
Iterations++;
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));
if (dwRetVal == NO_ERROR) {
pCurrAddresses = pAddresses;
while (pCurrAddresses) {
pUnicast = pCurrAddresses->FirstUnicastAddress;
while (pUnicast != NULL) {
sockaddr_in* address =
reinterpret_cast<sockaddr_in*>(pUnicast->Address.lpSockaddr);
InetNtop(PF_INET, &(address->sin_addr.s_addr), ip, sizeof(ip) - 1);
ip[49] = '\0';
addresses.emplace_back(std::string{ip});
pUnicast = pUnicast->Next;
}
pCurrAddresses = pCurrAddresses->Next;
}
}
if (pAddresses) {
FREE(pAddresses);
}
return addresses; // TODO
}
} // namespace cs

View File

@@ -57,6 +57,7 @@
static constexpr int NewImageMessage = 0x0400 + 4488;
static constexpr int SetCameraMessage = 0x0400 + 254;
static constexpr int WaitForStartupMessage = 0x0400 + 294;
static constexpr int PumpReadyMessage = 0x0400 + 330;
static constexpr char const* kPropWbValue = "WhiteBalance";
static constexpr char const* kPropExValue = "Exposure";
@@ -196,6 +197,7 @@ void UsbCameraImpl::Start() {
[this](HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
return this->PumpMain(hwnd, uiMsg, wParam, lParam);
});
m_messagePump->PostWindowMessage(PumpReadyMessage, nullptr, nullptr);
}
void UsbCameraImpl::PostRequestNewFrame() {
@@ -347,7 +349,7 @@ LRESULT UsbCameraImpl::PumpMain(HWND hwnd, UINT uiMsg, WPARAM wParam,
}
m_imageCallback.Reset();
break;
case WM_CREATE:
case PumpReadyMessage:
// Pump Created and ready to go
DeviceConnect();
break;
@@ -448,6 +450,15 @@ bool UsbCameraImpl::DeviceConnect() {
return false;
}
CS_Status st = 0;
auto devices = EnumerateUsbCameras(&st);
for (auto&& device : devices) {
if (device.path == m_path) {
SetDescription(device.name);
}
}
if (!m_properties_cached) {
SDEBUG3("caching properties");
DeviceCacheProperties();
@@ -486,21 +497,32 @@ bool UsbCameraImpl::CacheProperties(CS_Status* status) const {
return true;
}
void UsbCameraImpl::DeviceAddProperty(const wpi::Twine& name_,
tagVideoProcAmpProperty tag,
IAMVideoProcAmp* pProcAmp) {
template <typename TagProperty, typename IAM>
void UsbCameraImpl::DeviceAddProperty(const wpi::Twine& name_, TagProperty tag,
IAM* pProcAmp) {
// First see if properties exist
bool isValid = false;
auto property = std::make_unique<UsbCameraProperty>(name_, tag, false,
pProcAmp, &isValid);
if (isValid) {
DeviceCacheProperty(std::move(property), pProcAmp);
DeviceCacheProperty(std::move(property), m_sourceReader.Get());
}
}
template void UsbCameraImpl::DeviceAddProperty(const wpi::Twine& name_,
tagVideoProcAmpProperty tag,
IAMVideoProcAmp* pProcAmp);
template void UsbCameraImpl::DeviceAddProperty(const wpi::Twine& name_,
tagCameraControlProperty tag,
IAMCameraControl* pProcAmp);
#define CREATEPROPERTY(val) \
DeviceAddProperty(#val, VideoProcAmp_##val, pProcAmp);
#define CREATECONTROLPROPERTY(val) \
DeviceAddProperty(#val, CameraControl_##val, pCamControl);
void UsbCameraImpl::DeviceCacheProperties() {
if (!m_sourceReader) return;
@@ -521,6 +543,21 @@ void UsbCameraImpl::DeviceCacheProperties() {
CREATEPROPERTY(Gain)
pProcAmp->Release();
}
IAMCameraControl* pCamControl = NULL;
if (SUCCEEDED(m_sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(&pCamControl)))) {
CREATECONTROLPROPERTY(Pan)
CREATECONTROLPROPERTY(Tilt)
CREATECONTROLPROPERTY(Roll)
CREATECONTROLPROPERTY(Zoom)
CREATECONTROLPROPERTY(Exposure)
CREATECONTROLPROPERTY(Iris)
CREATECONTROLPROPERTY(Focus)
pCamControl->Release();
}
}
int UsbCameraImpl::RawToPercentage(const UsbCameraProperty& rawProp,
@@ -536,7 +573,7 @@ int UsbCameraImpl::PercentageToRaw(const UsbCameraProperty& rawProp,
}
void UsbCameraImpl::DeviceCacheProperty(
std::unique_ptr<UsbCameraProperty> rawProp, IAMVideoProcAmp* pProcAmp) {
std::unique_ptr<UsbCameraProperty> rawProp, IMFSourceReader* pProcAmp) {
// For percentage properties, we want to cache both the raw and the
// percentage versions. This function is always called with prop being
// the raw property (as it's coming from the camera) so if required, we need
@@ -705,16 +742,7 @@ CS_StatusValue UsbCameraImpl::DeviceCmdSetProperty(
if (!prop->device) {
if (prop->id == kPropConnectVerboseId) m_connectVerbose = value;
} else {
IAMVideoProcAmp* pProcAmp = NULL;
if (SUCCEEDED(m_sourceReader->GetServiceForStream(
(DWORD)MF_SOURCE_READER_MEDIASOURCE, GUID_NULL,
IID_PPV_ARGS(&pProcAmp)))) {
if (!prop->DeviceSet(lock, pProcAmp, value)) {
pProcAmp->Release();
return CS_PROPERTY_WRITE_FAILED;
}
pProcAmp->Release();
} else {
if (!prop->DeviceSet(lock, m_sourceReader.Get())) {
return CS_PROPERTY_WRITE_FAILED;
}
}
@@ -931,9 +959,7 @@ std::vector<UsbCameraInfo> EnumerateUsbCameras(CS_Status* status) {
// Ensure we are initialized by grabbing the message pump
// GetMessagePump();
ComPtr<IMFMediaSource> ppSource;
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
ComPtr<IMFMediaSource> pSource;
ComPtr<IMFAttributes> pAttributes;
IMFActivate** ppDevices = nullptr;
@@ -986,7 +1012,6 @@ done:
}
}
CoTaskMemFree(ppDevices);
pSource.Reset();
return retval;
}
@@ -1020,4 +1045,17 @@ std::string GetUsbCameraPath(CS_Source source, CS_Status* status) {
return static_cast<UsbCameraImpl&>(*data->source).GetPath();
}
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;
}
info.path = static_cast<UsbCameraImpl&>(*data->source).GetPath();
// TODO: dev and name
return info;
}
} // namespace cs

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
/* 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. */
@@ -132,11 +132,12 @@ class UsbCameraImpl : public SourceImpl,
CS_StatusValue DeviceSetMode();
void DeviceCacheMode();
void DeviceCacheProperty(std::unique_ptr<UsbCameraProperty> rawProp,
IAMVideoProcAmp* pProcAmp);
IMFSourceReader* sourceReader);
void DeviceCacheProperties();
void DeviceCacheVideoModes();
void DeviceAddProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
IAMVideoProcAmp* pProcAmp);
template <typename TagProperty, typename IAM>
void DeviceAddProperty(const wpi::Twine& name_, TagProperty tag,
IAM* pProcAmp);
ComPtr<IMFMediaType> DeviceCheckModeValid(const VideoMode& toCheck);

View File

@@ -7,13 +7,16 @@
#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->tag = tag;
this->tagVideoProc = tag;
this->isControlProperty = false;
this->isAutoProp = autoProp;
long paramVal, paramFlag; // NOLINT(runtime/int)
HRESULT hr;
@@ -42,7 +45,7 @@ bool UsbCameraProperty::DeviceGet(std::unique_lock<wpi::mutex>& lock,
lock.unlock();
long newValue = 0, paramFlag = 0; // NOLINT(runtime/int)
if (SUCCEEDED(pProcAmp->Get(tag, &newValue, &paramFlag))) {
if (SUCCEEDED(pProcAmp->Get(tagVideoProc, &newValue, &paramFlag))) {
lock.lock();
value = newValue;
return true;
@@ -60,10 +63,127 @@ bool UsbCameraProperty::DeviceSet(std::unique_lock<wpi::mutex>& lock,
if (!pProcAmp) return true;
lock.unlock();
if (SUCCEEDED(pProcAmp->Set(tag, newValue, VideoProcAmp_Flags_Manual))) {
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

@@ -7,6 +7,10 @@
#pragma once
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <memory>
#include <Dshow.h>
@@ -49,16 +53,23 @@ class UsbCameraProperty : public PropertyImpl {
UsbCameraProperty(const wpi::Twine& name_, tagVideoProcAmpProperty tag,
bool autoProp, IAMVideoProcAmp* pProcAmp, bool* isValid);
bool DeviceGet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp);
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,
IAMVideoProcAmp* pProcAmp) const;
bool DeviceSet(std::unique_lock<wpi::mutex>& lock, IAMVideoProcAmp* pProcAmp,
int newValue) const;
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};
tagVideoProcAmpProperty tag;
bool isControlProperty{false};
tagVideoProcAmpProperty tagVideoProc;
tagCameraControlProperty tagCameraControl;
// If this is a percentage (rather than raw) property
bool percentage{false};
@@ -68,5 +79,19 @@ class UsbCameraProperty : public PropertyImpl {
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

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

View File

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

View File

@@ -82,3 +82,4 @@ kResourceType_DigilentDMC60 = 80
kResourceType_PWMVictorSPX = 81
kResourceType_RevSparkMaxPWM = 82
kResourceType_RevSparkMaxCAN = 83
kResourceType_ADIS16470 = 84

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

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

View File

@@ -239,7 +239,7 @@ uint64_t HAL_GetFPGATime(int32_t* status) {
*status = NiFpga_Status_ResourceNotInitialized;
return 0;
}
*status = 0;
uint64_t upper1 = global->readLocalTimeUpper(status);
uint32_t lower = global->readLocalTime(status);
uint64_t upper2 = global->readLocalTimeUpper(status);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -112,16 +114,17 @@ class ConnectionListenerTest {
}
@Test
@ParameterizedTest
@DisabledOnOs(OS.WINDOWS)
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
void testThreaded() {
m_serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000);
@ValueSource(strings = { "127.0.0.1", "127.0.0.1 ", " 127.0.0.1 " })
void testThreaded(String address) {
m_serverInst.startServer("connectionlistenertest.ini", address, 10000);
List<ConnectionNotification> events = new ArrayList<>();
final int handle = m_serverInst.addConnectionListener(events::add, false);
// trigger a connect event
m_clientInst.startClient("127.0.0.1", 10000);
m_clientInst.startClient(address, 10000);
// wait for client to report it's started, then wait another 0.1 sec
try {

View File

@@ -90,6 +90,7 @@ class StorageTestPersistent : public StorageTestEmpty {
storage.SetEntryTypeValue("string/normal", Value::MakeString("hello"));
storage.SetEntryTypeValue("string/special",
Value::MakeString(StringRef("\0\3\5\n", 4)));
storage.SetEntryTypeValue("string/quoted", Value::MakeString("\"a\""));
storage.SetEntryTypeValue("raw/empty", Value::MakeRaw(""));
storage.SetEntryTypeValue("raw/normal", Value::MakeRaw("hello"));
storage.SetEntryTypeValue("raw/special",
@@ -599,6 +600,8 @@ TEST_P(StorageTestPersistent, SavePersistent) {
std::tie(line, rem) = rem.split('\n');
ASSERT_EQ("string \"string/normal\"=\"hello\"", line);
std::tie(line, rem) = rem.split('\n');
ASSERT_EQ("string \"string/quoted\"=\"\\\"a\\\"\"", line);
std::tie(line, rem) = rem.split('\n');
ASSERT_EQ("string \"string/special\"=\"\\x00\\x03\\x05\\n\"", line);
std::tie(line, rem) = rem.split('\n');
ASSERT_EQ("array string \"stringarr/empty\"=", line);
@@ -787,18 +790,19 @@ TEST_P(StorageTestEmpty, LoadPersistent) {
in += "string \"string/empty\"=\"\"\n";
in += "string \"string/normal\"=\"hello\"\n";
in += "string \"string/special\"=\"\\x00\\x03\\x05\\n\"\n";
in += "string \"string/quoted\"=\"\\\"a\\\"\"\n";
in += "array string \"stringarr/empty\"=\n";
in += "array string \"stringarr/one\"=\"hello\"\n";
in += "array string \"stringarr/two\"=\"hello\",\"world\\n\"\n";
EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(22);
EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(23);
EXPECT_CALL(notifier,
NotifyEntry(_, _, _, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX))
.Times(22);
.Times(23);
wpi::raw_mem_istream iss(in);
EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
ASSERT_EQ(22u, entries().size());
ASSERT_EQ(23u, entries().size());
EXPECT_EQ(*Value::MakeBoolean(true), *storage.GetEntryValue("boolean/true"));
EXPECT_EQ(*Value::MakeBoolean(false),
@@ -811,6 +815,8 @@ TEST_P(StorageTestEmpty, LoadPersistent) {
*storage.GetEntryValue("string/normal"));
EXPECT_EQ(*Value::MakeString(StringRef("\0\3\5\n", 4)),
*storage.GetEntryValue("string/special"));
EXPECT_EQ(*Value::MakeString("\"a\""),
*storage.GetEntryValue("string/quoted"));
EXPECT_EQ(*Value::MakeRaw(""), *storage.GetEntryValue("raw/empty"));
EXPECT_EQ(*Value::MakeRaw("hello"), *storage.GetEntryValue("raw/normal"));
EXPECT_EQ(*Value::MakeRaw(StringRef("\0\3\5\n", 4)),

View File

@@ -100,9 +100,8 @@ sourceSets {
dev
}
compileJava {
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile).configureEach {
options.compilerArgs = ['--release', '8']
}
dependencies {

View File

@@ -9,7 +9,7 @@ project.netCommComponents.each { String s->
netCommLibConfigs[s] = ['linux:athena']
}
def niLibrariesVersion = '2019.9.3'
def niLibrariesVersion = '2019.12.1'
model {
dependencyConfigs {

View File

@@ -1,4 +1,4 @@
def opencvVersion = '3.4.4-3'
def opencvVersion = '3.4.4-4'
if (project.hasProperty('useCpp') && project.useCpp) {
model {

View File

@@ -13,6 +13,11 @@ class ADXRS450_SpiGyroWrapperTest
: public ::testing::TestWithParam<frc::SPI::Port> {};
TEST_P(ADXRS450_SpiGyroWrapperTest, TestAccelerometer) {
#ifdef __APPLE__
// Disable test on mac, because of unknown failures
// TODO: Finally figure out the isse.
return;
#else
const double EPSILON = .000001;
frc::SPI::Port port = GetParam();
@@ -26,6 +31,7 @@ TEST_P(ADXRS450_SpiGyroWrapperTest, TestAccelerometer) {
sim.SetAngle(32.56);
EXPECT_NEAR(32.56, sim.GetAngle(), EPSILON);
EXPECT_NEAR(32.56, gyro.GetAngle(), EPSILON);
#endif
}
INSTANTIATE_TEST_CASE_P(

View File

@@ -26,6 +26,7 @@ DSCommPacket::DSCommPacket() {
i.ResetTcp();
i.ResetUdp();
}
matchInfo.gameSpecificMessageSize = 0;
}
/*----------------------------------------------------------------------------
@@ -95,7 +96,7 @@ void DSCommPacket::ReadJoystickTag(wpi::ArrayRef<uint8_t> dataInput,
int numBytes = (buttonCount + 7) / 8;
stick.buttons.buttons = 0;
for (int i = 0; i < numBytes; i++) {
stick.buttons.buttons |= dataInput[1 + i] << (8 * (i));
stick.buttons.buttons |= dataInput[numBytes - i] << (8 * (i));
}
stick.buttons.count = buttonCount;

View File

@@ -66,13 +66,21 @@ TEST_F(DSCommPacketTest, BlankJoystickTag) {
TEST_F(DSCommPacketTest, MainJoystickTag) {
for (int i = 0; i < HAL_kMaxJoysticks; i++) {
// Just random data
std::array<uint8_t, 12> _buttons{{0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1}};
std::array<uint8_t, 2> _button_bytes{{0, 0}};
for (int btn = 0; btn < 8; btn++) _button_bytes[1] |= _buttons[btn] << btn;
for (int btn = 8; btn < 12; btn++)
_button_bytes[0] |= _buttons[btn] << (btn - 8);
// 5 for base, 4 joystick, 12 buttons (2 bytes) 3 povs
uint8_t arr[5 + 4 + 2 + 6] = {// Size, Tag
16, 12,
// Axes
4, 0x9C, 0xCE, 0, 75,
// Buttons
12, 0xFF, 0x0F,
// Buttons (LSB 0)
12, _button_bytes[0], _button_bytes[1],
// POVs
3, 0, 50, 0, 100, 0x0F, 0x00};
@@ -80,6 +88,11 @@ TEST_F(DSCommPacketTest, MainJoystickTag) {
ASSERT_EQ(data.axes.count, 4);
ASSERT_EQ(data.povs.count, 3);
ASSERT_EQ(data.buttons.count, 12);
for (int btn = 0; btn < 12; btn++) {
ASSERT_EQ((data.buttons.buttons & (1 << btn)) != 0, _buttons[btn] != 0)
<< "Button " << btn;
}
}
}

View File

@@ -19,6 +19,7 @@
#include <wpi/StringRef.h>
#include "frc/AnalogInput.h"
#include "frc/MotorSafety.h"
#include "frc/Timer.h"
#include "frc/Utility.h"
#include "frc/WPIErrors.h"
@@ -548,10 +549,17 @@ void DriverStation::ReportJoystickUnpluggedWarning(const wpi::Twine& message) {
void DriverStation::Run() {
m_isRunning = true;
int safetyCounter = 0;
while (m_isRunning) {
HAL_WaitForDSData();
GetData();
if (IsDisabled()) safetyCounter = 0;
if (++safetyCounter >= 4) {
MotorSafety::CheckMotors();
safetyCounter = 0;
}
if (m_userInDisabled) HAL_ObserveUserProgramDisabled();
if (m_userInAutonomous) HAL_ObserveUserProgramAutonomous();
if (m_userInTeleop) HAL_ObserveUserProgramTeleop();

View File

@@ -26,29 +26,29 @@ IterativeRobotBase::IterativeRobotBase(double period)
m_watchdog(period, [this] { PrintLoopOverrunMessage(); }) {}
void IterativeRobotBase::RobotInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void IterativeRobotBase::DisabledInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void IterativeRobotBase::AutonomousInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void IterativeRobotBase::TeleopInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void IterativeRobotBase::TestInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void IterativeRobotBase::RobotPeriodic() {
static bool firstRun = true;
if (firstRun) {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
firstRun = false;
}
}
@@ -56,7 +56,7 @@ void IterativeRobotBase::RobotPeriodic() {
void IterativeRobotBase::DisabledPeriodic() {
static bool firstRun = true;
if (firstRun) {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
firstRun = false;
}
}
@@ -64,7 +64,7 @@ void IterativeRobotBase::DisabledPeriodic() {
void IterativeRobotBase::AutonomousPeriodic() {
static bool firstRun = true;
if (firstRun) {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
firstRun = false;
}
}
@@ -72,7 +72,7 @@ void IterativeRobotBase::AutonomousPeriodic() {
void IterativeRobotBase::TeleopPeriodic() {
static bool firstRun = true;
if (firstRun) {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
firstRun = false;
}
}
@@ -80,7 +80,7 @@ void IterativeRobotBase::TeleopPeriodic() {
void IterativeRobotBase::TestPeriodic() {
static bool firstRun = true;
if (firstRun) {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
firstRun = false;
}
}

View File

@@ -10,53 +10,66 @@
#include <algorithm>
#include <utility>
#include <wpi/SmallPtrSet.h>
#include <wpi/SmallString.h>
#include <wpi/raw_ostream.h>
#include "frc/DriverStation.h"
#include "frc/WPIErrors.h"
using namespace frc;
MotorSafety::MotorSafety(MotorSafety&& rhs)
: ErrorBase(std::move(rhs)), m_enabled(std::move(rhs.m_enabled)) {
m_watchdog = Watchdog(rhs.m_watchdog.GetTimeout(), [this] { TimeoutFunc(); });
static wpi::SmallPtrSet<MotorSafety*, 32> instanceList;
static wpi::mutex listMutex;
MotorSafety::MotorSafety() {
std::lock_guard<wpi::mutex> lock(listMutex);
instanceList.insert(this);
}
MotorSafety::~MotorSafety() {
std::lock_guard<wpi::mutex> lock(listMutex);
instanceList.erase(this);
}
MotorSafety::MotorSafety(MotorSafety&& rhs)
: ErrorBase(std::move(rhs)),
m_expiration(std::move(rhs.m_expiration)),
m_enabled(std::move(rhs.m_enabled)),
m_stopTime(std::move(rhs.m_stopTime)) {}
MotorSafety& MotorSafety::operator=(MotorSafety&& rhs) {
ErrorBase::operator=(std::move(rhs));
m_watchdog = Watchdog(rhs.m_watchdog.GetTimeout(), [this] { TimeoutFunc(); });
m_expiration = std::move(rhs.m_expiration);
m_enabled = std::move(rhs.m_enabled);
m_stopTime = std::move(rhs.m_stopTime);
return *this;
}
void MotorSafety::Feed() {
std::lock_guard<wpi::mutex> lock(m_thisMutex);
m_watchdog.Reset();
m_stopTime = Timer::GetFPGATimestamp() + m_expiration;
}
void MotorSafety::SetExpiration(double expirationTime) {
std::lock_guard<wpi::mutex> lock(m_thisMutex);
m_watchdog.SetTimeout(expirationTime);
m_expiration = expirationTime;
}
double MotorSafety::GetExpiration() const {
std::lock_guard<wpi::mutex> lock(m_thisMutex);
return m_watchdog.GetTimeout();
return m_expiration;
}
bool MotorSafety::IsAlive() const {
std::lock_guard<wpi::mutex> lock(m_thisMutex);
return !m_enabled || !m_watchdog.IsExpired();
return !m_enabled || m_stopTime > Timer::GetFPGATimestamp();
}
void MotorSafety::SetSafetyEnabled(bool enabled) {
std::lock_guard<wpi::mutex> lock(m_thisMutex);
if (enabled) {
m_watchdog.Enable();
} else {
m_watchdog.Disable();
}
m_enabled = enabled;
}
@@ -65,16 +78,34 @@ bool MotorSafety::IsSafetyEnabled() const {
return m_enabled;
}
void MotorSafety::TimeoutFunc() {
auto& ds = DriverStation::GetInstance();
if (ds.IsDisabled() || ds.IsTest()) {
void MotorSafety::Check() {
bool enabled;
double stopTime;
{
std::lock_guard<wpi::mutex> lock(m_thisMutex);
enabled = m_enabled;
stopTime = m_stopTime;
}
DriverStation& ds = DriverStation::GetInstance();
if (!enabled || ds.IsDisabled() || ds.IsTest()) {
return;
}
wpi::SmallString<128> buf;
wpi::raw_svector_ostream desc(buf);
GetDescription(desc);
desc << "... Output not updated often enough.";
wpi_setWPIErrorWithContext(Timeout, desc.str());
StopMotor();
if (stopTime < Timer::GetFPGATimestamp()) {
wpi::SmallString<128> buf;
wpi::raw_svector_ostream desc(buf);
GetDescription(desc);
desc << "... Output not updated often enough.";
wpi_setWPIErrorWithContext(Timeout, desc.str());
StopMotor();
}
}
void MotorSafety::CheckMotors() {
std::lock_guard<wpi::mutex> lock(listMutex);
for (auto elem : instanceList) {
elem->Check();
}
}

View File

@@ -98,13 +98,15 @@ RobotBase::RobotBase() : m_ds(DriverStation::GetInstance()) {
SmartDashboard::init();
std::FILE* file = nullptr;
file = std::fopen("/tmp/frc_versions/FRC_Lib_Version.ini", "w");
if (IsReal()) {
std::FILE* file = nullptr;
file = std::fopen("/tmp/frc_versions/FRC_Lib_Version.ini", "w");
if (file != nullptr) {
std::fputs("C++ ", file);
std::fputs(GetWPILibVersion(), file);
std::fclose(file);
if (file != nullptr) {
std::fputs("C++ ", file);
std::fputs(GetWPILibVersion(), file);
std::fclose(file);
}
}
// First and one-time initialization

View File

@@ -59,23 +59,23 @@ void SampleRobot::StartCompetition() {
}
void SampleRobot::RobotInit() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void SampleRobot::Disabled() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void SampleRobot::Autonomous() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void SampleRobot::OperatorControl() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void SampleRobot::Test() {
wpi::outs() << "Default " << __FUNCTION__ << "() method... Overload me!\n";
wpi::outs() << "Default " << __FUNCTION__ << "() method... Override me!\n";
}
void SampleRobot::RobotMain() { m_robotMainOverridden = false; }

View File

@@ -18,7 +18,7 @@ constexpr std::chrono::milliseconds Watchdog::kMinPrintPeriod;
class Watchdog::Thread : public wpi::SafeThread {
public:
template <typename T>
struct DerefGreater : public std::binary_function<T, T, bool> {
struct DerefGreater {
constexpr bool operator()(const T& lhs, const T& rhs) const {
return *lhs > *rhs;
}
@@ -51,15 +51,22 @@ void Watchdog::Thread::Main() {
auto now = hal::fpga_clock::now();
if (now - watchdog->m_lastTimeoutPrintTime > kMinPrintPeriod) {
watchdog->m_lastTimeoutPrintTime = now;
wpi::outs() << "Watchdog not fed within "
<< wpi::format("%.6f",
watchdog->m_timeout.count() / 1.0e6)
<< "s\n";
if (!watchdog->m_suppressTimeoutMessage) {
wpi::outs() << "Watchdog not fed within "
<< wpi::format("%.6f",
watchdog->m_timeout.count() / 1.0e6)
<< "s\n";
}
}
// Set expiration flag before calling the callback so any manipulation
// of the flag in the callback (e.g., calling Disable()) isn't
// clobbered.
watchdog->m_isExpired = true;
lock.unlock();
watchdog->m_callback();
lock.lock();
watchdog->m_isExpired = true;
}
// Otherwise, a Watchdog removed itself from the queue (it notifies the
// scheduler of this) or a spurious wakeup occurred, so just rewait with
@@ -153,12 +160,14 @@ void Watchdog::Disable() {
auto thr = m_owner->GetThread();
if (!thr) return;
m_isExpired = false;
thr->m_watchdogs.remove(this);
thr->m_cond.notify_all();
}
void Watchdog::SuppressTimeoutMessage(bool suppress) {
m_suppressTimeoutMessage = suppress;
}
bool Watchdog::operator>(const Watchdog& rhs) {
return m_expirationTime > rhs.m_expirationTime;
}

View File

@@ -0,0 +1,12 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/LayoutType.h"
using namespace frc;
wpi::StringRef LayoutType::GetLayoutName() const { return m_layoutName; }

View File

@@ -0,0 +1,46 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/SendableCameraWrapper.h"
#include <cscore_oo.h>
#include <wpi/DenseMap.h>
#include "frc/smartdashboard/SendableBuilder.h"
using namespace frc;
namespace {
constexpr const char* kProtocol = "camera_server://";
wpi::DenseMap<int, std::unique_ptr<SendableCameraWrapper>> wrappers;
} // namespace
SendableCameraWrapper& SendableCameraWrapper::Wrap(
const cs::VideoSource& source) {
return Wrap(source.GetHandle());
}
SendableCameraWrapper& SendableCameraWrapper::Wrap(CS_Source source) {
auto& wrapper = wrappers[static_cast<int>(source)];
if (!wrapper)
wrapper = std::make_unique<SendableCameraWrapper>(source, private_init{});
return *wrapper;
}
SendableCameraWrapper::SendableCameraWrapper(CS_Source source,
const private_init&)
: SendableBase(false), m_uri(kProtocol) {
CS_Status status = 0;
auto name = cs::GetSourceName(source, &status);
SetName(name);
m_uri += name;
}
void SendableCameraWrapper::InitSendable(SendableBuilder& builder) {
builder.AddStringProperty(".ShuffleboardURI", [this] { return m_uri; },
nullptr);
}

View File

@@ -11,12 +11,19 @@
#include <wpi/raw_ostream.h>
#include "frc/shuffleboard/ComplexWidget.h"
#include "frc/shuffleboard/SendableCameraWrapper.h"
#include "frc/shuffleboard/ShuffleboardComponent.h"
#include "frc/shuffleboard/ShuffleboardLayout.h"
#include "frc/shuffleboard/SimpleWidget.h"
using namespace frc;
static constexpr const char* layoutStrings[] = {"List Layout", "Grid Layout"};
static constexpr const char* GetStringFromBuiltInLayout(BuiltInLayouts layout) {
return layoutStrings[static_cast<int>(layout)];
}
ShuffleboardContainer::ShuffleboardContainer(const wpi::Twine& title)
: ShuffleboardValue(title) {}
@@ -25,8 +32,18 @@ ShuffleboardContainer::GetComponents() const {
return m_components;
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& type,
const wpi::Twine& title) {
ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title,
BuiltInLayouts type) {
return GetLayout(title, GetStringFromBuiltInLayout(type));
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title,
const LayoutType& type) {
return GetLayout(title, type.GetLayoutName());
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title,
const wpi::Twine& type) {
wpi::SmallVector<char, 16> storage;
auto titleRef = title.toStringRef(storage);
if (m_layouts.count(titleRef) == 0) {
@@ -38,6 +55,16 @@ ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& type,
return *m_layouts[titleRef];
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(const wpi::Twine& title) {
wpi::SmallVector<char, 16> storage;
auto titleRef = title.toStringRef(storage);
if (m_layouts.count(titleRef) == 0) {
wpi_setWPIErrorWithContext(
InvalidParameter, "No layout with the given title has been defined");
}
return *m_layouts[titleRef];
}
ComplexWidget& ShuffleboardContainer::Add(const wpi::Twine& title,
Sendable& sendable) {
CheckTitle(title);
@@ -47,6 +74,11 @@ ComplexWidget& ShuffleboardContainer::Add(const wpi::Twine& title,
return *ptr;
}
ComplexWidget& ShuffleboardContainer::Add(const wpi::Twine& title,
const cs::VideoSource& video) {
return Add(title, SendableCameraWrapper::Wrap(video));
}
ComplexWidget& ShuffleboardContainer::Add(Sendable& sendable) {
if (sendable.GetName().empty()) {
wpi::outs() << "Sendable must have a name\n";
@@ -54,6 +86,10 @@ ComplexWidget& ShuffleboardContainer::Add(Sendable& sendable) {
return Add(sendable.GetName(), sendable);
}
ComplexWidget& ShuffleboardContainer::Add(const cs::VideoSource& video) {
return Add(SendableCameraWrapper::Wrap(video));
}
SimpleWidget& ShuffleboardContainer::Add(
const wpi::Twine& title, std::shared_ptr<nt::Value> defaultValue) {
CheckTitle(title);

View File

@@ -0,0 +1,41 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/ShuffleboardWidget.h"
using namespace frc;
static constexpr const char* widgetStrings[] = {
"Text View",
"Number Slider",
"Number Bar",
"Simple Dial",
"Graph",
"Boolean Box",
"Toggle Button",
"Toggle Switch",
"Voltage View",
"PDP",
"ComboBox Chooser",
"Split Button Chooser",
"Encoder",
"Speed Controller",
"Command",
"PID Command",
"PID Controller",
"Accelerometer",
"3-Axis Accelerometer",
"Gyro",
"Relay",
"Differential Drivebase",
"Mecanum Drivebase",
"Camera Stream",
};
const char* detail::GetStringForWidgetType(BuiltInWidgets type) {
return widgetStrings[static_cast<int>(type)];
}

View File

@@ -0,0 +1,12 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/WidgetType.h"
using namespace frc;
wpi::StringRef WidgetType::GetWidgetName() const { return m_widgetName; }

View File

@@ -12,6 +12,7 @@
#include "frc/RobotBase.h"
namespace frc {
/** WPILib FileSystem namespace */
namespace filesystem {
/**

View File

@@ -16,9 +16,9 @@ namespace frc {
/**
* Gamepad Interface.
*/
class WPI_DEPRECATED("Inherit directly from GenericHID instead.") GamepadBase
: public GenericHID {
class GamepadBase : public GenericHID {
public:
WPI_DEPRECATED("Inherit directly from GenericHID instead.")
explicit GamepadBase(int port);
virtual ~GamepadBase() = default;

View File

@@ -26,7 +26,8 @@ namespace frc {
*
* Init() functions -- each of the following functions is called once when the
* appropriate mode is entered:
* - DisabledInit() -- called only when first disabled
* - DisabledInit() -- called each and every time disabled is entered from
* another mode
* - AutonomousInit() -- called each and every time autonomous is entered from
* another mode
* - TeleopInit() -- called each and every time teleop is entered from

View File

@@ -16,9 +16,9 @@ namespace frc {
/**
* Joystick Interface.
*/
class WPI_DEPRECATED("Inherit directly from GenericHID instead.") JoystickBase
: public GenericHID {
class JoystickBase : public GenericHID {
public:
WPI_DEPRECATED("Inherit directly from GenericHID instead.")
explicit JoystickBase(int port);
virtual ~JoystickBase() = default;

View File

@@ -10,10 +10,8 @@
#include <wpi/mutex.h>
#include <wpi/raw_ostream.h>
#include "frc/DriverStation.h"
#include "frc/ErrorBase.h"
#include "frc/Timer.h"
#include "frc/Watchdog.h"
namespace frc {
@@ -25,8 +23,8 @@ namespace frc {
*/
class MotorSafety : public ErrorBase {
public:
MotorSafety() = default;
virtual ~MotorSafety() = default;
MotorSafety();
virtual ~MotorSafety();
MotorSafety(MotorSafety&& rhs);
MotorSafety& operator=(MotorSafety&& rhs);
@@ -77,20 +75,39 @@ class MotorSafety : public ErrorBase {
*/
bool IsSafetyEnabled() const;
/**
* Check if this motor has exceeded its timeout.
*
* This method is called periodically to determine if this motor has exceeded
* its timeout value. If it has, the stop method is called, and the motor is
* shut down until its value is updated again.
*/
void Check();
/**
* Check the motors to see if any have timed out.
*
* This static method is called periodically to poll all the motors and stop
* any that have timed out.
*/
static void CheckMotors();
virtual void StopMotor() = 0;
virtual void GetDescription(wpi::raw_ostream& desc) const = 0;
private:
static constexpr double kDefaultSafetyExpiration = 0.1;
Watchdog m_watchdog{kDefaultSafetyExpiration, [this] { TimeoutFunc(); }};
// The expiration time for this object
double m_expiration = kDefaultSafetyExpiration;
// True if motor safety is enabled for this motor
bool m_enabled = false;
mutable wpi::mutex m_thisMutex;
// The FPGA clock value when the motor has expired
double m_stopTime = Timer::GetFPGATimestamp();
void TimeoutFunc();
mutable wpi::mutex m_thisMutex;
};
} // namespace frc

View File

@@ -19,6 +19,8 @@ enum class PIDSourceType { kDisplacement, kRate };
*/
class PIDSource {
public:
virtual ~PIDSource() = default;
/**
* Set which parameter you are using as a process control variable.
*

View File

@@ -13,11 +13,7 @@
namespace frc {
class WPI_DEPRECATED(
"WARNING: While it may look like a good choice to use for your code if "
"you're inexperienced, don't. Unless you know what you are doing, complex "
"code will be much more difficult under this system. Use TimedRobot or "
"Command-Based instead.") SampleRobot : public RobotBase {
class SampleRobot : public RobotBase {
public:
/**
* Start a competition.
@@ -95,6 +91,11 @@ class WPI_DEPRECATED(
virtual void RobotMain();
protected:
WPI_DEPRECATED(
"WARNING: While it may look like a good choice to use for your code if "
"you're inexperienced, don't. Unless you know what you are doing, "
"complex code will be much more difficult under this system. Use "
"TimedRobot or Command-Based instead.")
SampleRobot();
virtual ~SampleRobot() = default;

View File

@@ -82,7 +82,7 @@ class SerialPort : public ErrorBase {
* @param stopBits The number of stop bits to use as defined by the enum
* StopBits.
*/
WPI_DEPRECATED("Will be removed for 2019")
WPI_DEPRECATED("Will be removed for 2020")
SerialPort(int baudRate, const wpi::Twine& portName, Port port = kOnboard,
int dataBits = 8, Parity parity = kParity_None,
StopBits stopBits = kStopBits_One);

View File

@@ -75,6 +75,7 @@ S(SmartDashboardMissingKey, -43, "SmartDashboard data does not exist");
S(CommandIllegalUse, -50, "Illegal use of Command");
S(UnsupportedInSimulation, -80, "Unsupported in simulation");
S(CameraServerError, -90, "CameraServer error");
S(InvalidParameter, -100, "Invalid parameter value");
// Warnings
S(SampleRateTooHigh, 1, "Analog module sample rate is too high");

View File

@@ -104,6 +104,16 @@ class Watchdog {
*/
void Disable();
/**
* Enable or disable suppression of the generic timeout message.
*
* This may be desirable if the user-provided callback already prints a more
* specific message.
*
* @param suppress Whether to suppress generic timeout message.
*/
void SuppressTimeoutMessage(bool suppress);
private:
// Used for timeout print rate-limiting
static constexpr std::chrono::milliseconds kMinPrintPeriod{1000};
@@ -118,6 +128,8 @@ class Watchdog {
wpi::StringMap<std::chrono::microseconds> m_epochs;
bool m_isExpired = false;
bool m_suppressTimeoutMessage = false;
class Thread;
wpi::SafeThreadOwner<Thread>* m_owner;

View File

@@ -19,9 +19,9 @@ namespace frc {
* Live Window Sendable is a special type of object sendable to the live window.
* @deprecated Use Sendable directly instead
*/
class WPI_DEPRECATED("use Sendable directly instead") LiveWindowSendable
: public Sendable {
class LiveWindowSendable : public Sendable {
public:
WPI_DEPRECATED("use Sendable directly instead")
LiveWindowSendable() = default;
LiveWindowSendable(LiveWindowSendable&&) = default;
LiveWindowSendable& operator=(LiveWindowSendable&&) = default;

View File

@@ -0,0 +1,52 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/LayoutType.h"
namespace frc {
/**
* The types of layouts bundled with Shuffleboard.
*
* <pre>{@code
* ShuffleboardLayout myList = Shuffleboard::GetTab("My Tab")
* .GetLayout(BuiltinLayouts::kList, "My List");
* }</pre>
*/
enum class BuiltInLayouts {
/**
* Groups components in a vertical list. New widgets added to the layout will
* be placed at the bottom of the list. <br>Custom properties: <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Label position</td><td>String</td><td>"BOTTOM"</td>
* <td>The position of component labels inside the grid. One of
* {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}</td></tr>
* </table>
*/
kList,
/**
* Groups components in an <i>n</i> x <i>m</i> grid. Grid layouts default to
* 3x3. <br>Custom properties: <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Number of columns</td><td>Number</td><td>3</td><td>Must be in the
* range [1,15]</td>
* </tr>
* <tr><td>Number of rows</td><td>Number</td><td>3</td><td>Must be in the
* range [1,15]</td></tr> <tr> <td>Label position</td> <td>String</td>
* <td>"BOTTOM"</td>
* <td>The position of component labels inside the grid.
* One of {@code ["TOP", "LEFT", "BOTTOM", "RIGHT", "HIDDEN"}</td>
* </tr>
* </table>
*/
kGrid
};
} // namespace frc

View File

@@ -0,0 +1,386 @@
/*----------------------------------------------------------------------------*/
/* 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 "frc/shuffleboard/WidgetType.h"
namespace frc {
/**
* The types of the widgets bundled with Shuffleboard.
*
* <p>For example, setting a number to be displayed with a slider:
* <pre>{@code
* NetworkTableEntry example = Shuffleboard.getTab("My Tab")
* .add("My Number", 0)
* .withWidget(BuiltInWidgets.kNumberSlider)
* .getEntry();
* }</pre>
*
* <p>Each value in this enum goes into detail on what data types that widget
* can support, as well as the custom properties that widget uses.
*/
enum class BuiltInWidgets {
/**
* Displays a value with a simple text field.
* <br>Supported types:
* <ul>
* <li>String</li>
* <li>Number</li>
* <li>Boolean</li>
* </ul>
* <br>This widget has no custom properties.
*/
kTextView,
/**
* Displays a number with a controllable slider.
* <br>Supported types:
* <ul>
* <li>Number</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Min</td><td>Number</td><td>-1.0</td><td>The minimum value of the
* slider</td></tr> <tr><td>Max</td><td>Number</td><td>1.0</td><td>The maximum
* value of the slider</td></tr> <tr><td>Block
* increment</td><td>Number</td><td>0.0625</td> <td>How much to move the
* slider by with the arrow keys</td></tr>
* </table>
*/
kNumberSlider,
/**
* Displays a number with a view-only bar.
* <br>Supported types:
* <ul>
* <li>Number</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Min</td><td>Number</td><td>-1.0</td><td>The minimum value of the
* bar</td></tr> <tr><td>Max</td><td>Number</td><td>1.0</td><td>The maximum
* value of the bar</td></tr>
* <tr><td>Center</td><td>Number</td><td>0</td><td>The center ("zero") value
* of the bar</td></tr>
* </table>
*/
kNumberBar,
/**
* Displays a number with a view-only dial. Displayed values are rounded to
* the nearest integer. <br>Supported types: <ul> <li>Number</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Min</td><td>Number</td><td>0</td><td>The minimum value of the
* dial</td></tr> <tr><td>Max</td><td>Number</td><td>100</td><td>The maximum
* value of the dial</td></tr> <tr><td>Show
* value</td><td>Boolean</td><td>true</td> <td>Whether or not to show the
* value as text</td></tr>
* </table>
*/
kDial,
/**
* Displays a number with a graph. <strong>NOTE:</strong> graphs can be taxing
* on the computer running the dashboard. Keep the number of visible data
* points to a minimum. Making the widget smaller also helps with performance,
* but may cause the graph to become difficult to read. <br>Supported types:
* <ul>
* <li>Number</li>
* <li>Number array</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Visible time</td><td>Number</td><td>30</td>
* <td>How long, in seconds, should past data be visible for</td></tr>
* </table>
*/
kGraph,
/**
* Displays a boolean value as a large colored box.
* <br>Supported types:
* <ul>
* <li>Boolean</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Color when true</td><td>Color</td><td>"green"</td>
* <td>Can be specified as a string ({@code "#00FF00"}) or a rgba integer
* ({@code 0x00FF0000})
* </td></tr>
* <tr><td>Color when false</td><td>Color</td><td>"red"</td>
* <td>Can be specified as a string or a number</td></tr>
* </table>
*/
kBooleanBox,
/**
* Displays a boolean with a large interactive toggle button.
* <br>Supported types:
* <ul>
* <li>Boolean</li>
* </ul>
* <br>This widget has no custom properties.
*/
kToggleButton,
/**
* Displays a boolean with a fixed-size toggle switch.
* <br>Supported types:
* <ul>
* <li>Boolean</li>
* </ul>
* <br>This widget has no custom properties.
*/
kToggleSwitch,
/**
* Displays an analog input or a raw number with a number bar.
* <br>Supported types:
* <ul>
* <li>Number</li>
* <li>{@link edu.wpi.first.wpilibj.AnalogInput}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Min</td><td>Number</td><td>0</td><td>The minimum value of the
* bar</td></tr> <tr><td>Max</td><td>Number</td><td>5</td><td>The maximum
* value of the bar</td></tr>
* <tr><td>Center</td><td>Number</td><td>0</td><td>The center ("zero") value
* of the bar</td></tr>
* <tr><td>Orientation</td><td>String</td><td>"HORIZONTAL"</td>
* <td>The orientation of the bar. One of {@code ["HORIZONTAL",
* "VERTICAL"]}</td></tr> <tr><td>Number of tick
* marks</td><td>Number</td><td>5</td> <td>The number of discrete ticks on the
* bar</td></tr>
* </table>
*/
kVoltageView,
/**
* Displays a {@link edu.wpi.first.wpilibj.PowerDistributionPanel
* PowerDistributionPanel}. <br>Supported types: <ul> <li>{@link
* edu.wpi.first.wpilibj.PowerDistributionPanel}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Show voltage and current values</td><td>Boolean</td><td>true</td>
* <td>Whether or not to display the voltage and current draw</td></tr>
* </table>
*/
kPowerDistributionPanel,
/**
* Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser
* SendableChooser} with a dropdown combo box with a list of options.
* <br>Supported types:
* <ul>
* <li>{@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kComboBoxChooser,
/**
* Displays a {@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser
* SendableChooser} with a toggle button for each available option.
* <br>Supported types:
* <ul>
* <li>{@link edu.wpi.first.wpilibj.smartdashboard.SendableChooser}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kSplitButtonChooser,
/**
* Displays an {@link edu.wpi.first.wpilibj.Encoder} displaying its speed,
* total travelled distance, and its distance per tick. <br>Supported types:
* <ul>
* <li>{@link edu.wpi.first.wpilibj.Encoder}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kEncoder,
/**
* Displays a {@link edu.wpi.first.wpilibj.SpeedController SpeedController}.
* The speed controller will be controllable from the dashboard when test mode
* is enabled, but will otherwise be view-only. <br>Supported types: <ul>
* <li>{@link edu.wpi.first.wpilibj.PWMSpeedController}</li>
* <li>{@link edu.wpi.first.wpilibj.DMC60}</li>
* <li>{@link edu.wpi.first.wpilibj.Jaguar}</li>
* <li>{@link edu.wpi.first.wpilibj.PWMTalonSRX}</li>
* <li>{@link edu.wpi.first.wpilibj.PWMVictorSPX}</li>
* <li>{@link edu.wpi.first.wpilibj.SD540}</li>
* <li>{@link edu.wpi.first.wpilibj.Spark}</li>
* <li>{@link edu.wpi.first.wpilibj.Talon}</li>
* <li>{@link edu.wpi.first.wpilibj.Victor}</li>
* <li>{@link edu.wpi.first.wpilibj.VictorSP}</li>
* <li>{@link edu.wpi.first.wpilibj.SpeedControllerGroup}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Orientation</td><td>String</td><td>"HORIZONTAL"</td>
* <td>One of {@code ["HORIZONTAL", "VERTICAL"]}</td></tr>
* </table>
*/
kSpeedController,
/**
* Displays a command with a toggle button. Pressing the button will start the
* command, and the button will automatically release when the command
* completes. <br>Supported types: <ul> <li>{@link
* edu.wpi.first.wpilibj.command.Command}</li> <li>{@link
* edu.wpi.first.wpilibj.command.CommandGroup}</li> <li>Any custom subclass of
* {@code Command} or {@code CommandGroup}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kCommand,
/**
* Displays a PID command with a checkbox and an editor for the PIDF
* constants. Selecting the checkbox will start the command, and the checkbox
* will automatically deselect when the command completes. <br>Supported
* types: <ul> <li>{@link edu.wpi.first.wpilibj.command.PIDCommand}</li>
* <li>Any custom subclass of {@code PIDCommand}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kPIDCommand,
/**
* Displays a PID controller with an editor for the PIDF constants and a
* toggle switch for enabling and disabling the controller. <br>Supported
* types: <ul> <li>{@link edu.wpi.first.wpilibj.PIDController}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kPIDController,
/**
* Displays an accelerometer with a number bar displaying the magnitude of the
* acceleration and text displaying the exact value. <br>Supported types: <ul>
* <li>{@link edu.wpi.first.wpilibj.AnalogAccelerometer}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Min</td><td>Number</td><td>-1</td>
* <td>The minimum acceleration value to display</td></tr>
* <tr><td>Max</td><td>Number</td><td>1</td>
* <td>The maximum acceleration value to display</td></tr>
* <tr><td>Show text</td><td>Boolean</td><td>true</td>
* <td>Show or hide the acceleration values</td></tr>
* <tr><td>Precision</td><td>Number</td><td>2</td>
* <td>How many numbers to display after the decimal point</td></tr>
* <tr><td>Show tick marks</td><td>Boolean</td><td>false</td>
* <td>Show or hide the tick marks on the number bars</td></tr>
* </table>
*/
kAccelerometer,
/**
* Displays a 3-axis accelerometer with a number bar for each axis'
* accleration. <br>Supported types: <ul> <li>{@link
* edu.wpi.first.wpilibj.ADXL345_I2C}</li> <li>{@link
* edu.wpi.first.wpilibj.ADXL345_SPI}</li> <li>{@link
* edu.wpi.first.wpilibj.ADXL362}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Range</td><td>{@link Range}</td><td>k16G</td><td>The accelerometer
* range</td></tr> <tr><td>Show value</td><td>Boolean</td><td>true</td>
* <td>Show or hide the acceleration values</td></tr>
* <tr><td>Precision</td><td>Number</td><td>2</td>
* <td>How many numbers to display after the decimal point</td></tr>
* <tr><td>Show tick marks</td><td>Boolean</td><td>false</td>
* <td>Show or hide the tick marks on the number bars</td></tr>
* </table>
*/
k3AxisAccelerometer,
/**
* Displays a gyro with a dial from 0 to 360 degrees.
* <br>Supported types:
* <ul>
* <li>{@link edu.wpi.first.wpilibj.ADXRS450_Gyro}</li>
* <li>{@link edu.wpi.first.wpilibj.AnalogGyro}</li>
* <li>Any custom subclass of {@code GyroBase} (such as a MXP gyro)</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Major tick
* spacing</td><td>Number</td><td>45</td><td>Degrees</td></tr>
* <tr><td>Starting angle</td><td>Number</td><td>180</td>
* <td>How far to rotate the entire dial, in degrees</td></tr>
* <tr><td>Show tick mark ring</td><td>Boolean</td><td>true</td></tr>
* </table>
*/
kGyro,
/**
* Displays a relay with toggle buttons for each supported mode (off, on,
* forward, reverse). <br>Supported types: <ul> <li>{@link
* edu.wpi.first.wpilibj.Relay}</li>
* </ul>
* <br>This widget has no custom properties.
*/
kRelay,
/**
* Displays a differential drive with a widget that displays the speed of each
* side of the drivebase and a vector for the direction and rotation of the
* drivebase. The widget will be controllable if the robot is in test mode.
* <br>Supported types:
* <ul>
* <li>{@link edu.wpi.first.wpilibj.drive.DifferentialDrive}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Number of wheels</td><td>Number</td><td>4</td><td>Must be a
* positive even integer
* </td></tr>
* <tr><td>Wheel diameter</td><td>Number</td><td>80</td><td>Pixels</td></tr>
* <tr><td>Show velocity vectors</td><td>Boolean</td><td>true</td></tr>
* </table>
*/
kDifferentialDrive,
/**
* Displays a mecanum drive with a widget that displays the speed of each
* wheel, and vectors for the direction and rotation of the drivebase. The
* widget will be controllable if the robot is in test mode. <br>Supported
* types: <ul> <li>{@link edu.wpi.first.wpilibj.drive.MecanumDrive}</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Show velocity vectors</td><td>Boolean</td><td>true</td></tr>
* </table>
*/
kMecanumDrive,
/**
* Displays a camera stream.
* <br>Supported types:
* <ul>
* <li>{@link edu.wpi.cscore.VideoSource} (as long as it is streaming on an
* MJPEG server)</li>
* </ul>
* <br>Custom properties:
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></tr>
* <tr><td>Show crosshair</td><td>Boolean</td><td>true</td>
* <td>Show or hide a crosshair on the image</td></tr>
* <tr><td>Crosshair color</td><td>Color</td><td>"white"</td>
* <td>Can be a string or a rgba integer</td></tr>
* <tr><td>Show controls</td><td>Boolean</td><td>true</td><td>Show or hide the
* stream controls
* </td></tr>
* <tr><td>Rotation</td><td>String</td><td>"NONE"</td>
* <td>Rotates the displayed image. One of {@code ["NONE", "QUARTER_CW",
* "QUARTER_CCW", "HALF"]}
* </td></tr>
* </table>
*/
kCameraStream
};
} // namespace frc

View File

@@ -0,0 +1,37 @@
/*----------------------------------------------------------------------------*/
/* 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 <wpi/StringRef.h>
namespace frc {
/**
* Represents the type of a layout in Shuffleboard. Using this is preferred over
* specifying raw strings, to avoid typos and having to know or look up the
* exact string name for a desired layout.
*
* @see BuiltInLayouts the built-in layout types
*/
class LayoutType {
public:
explicit constexpr LayoutType(const char* layoutName)
: m_layoutName(layoutName) {}
~LayoutType() = default;
/**
* Gets the string type of the layout as defined by that layout in
* Shuffleboard.
*/
wpi::StringRef GetLayoutName() const;
private:
const char* m_layoutName;
};
} // namespace frc

View File

@@ -0,0 +1,55 @@
/*----------------------------------------------------------------------------*/
/* 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 <string>
#include <cscore_c.h>
#include "frc/smartdashboard/SendableBase.h"
namespace cs {
class VideoSource;
} // namespace cs
namespace frc {
/**
* A wrapper to make video sources sendable and usable from Shuffleboard.
*/
class SendableCameraWrapper : public SendableBase {
private:
struct private_init {};
public:
/**
* Creates a new sendable wrapper. Private constructor to avoid direct
* instantiation with multiple wrappers floating around for the same camera.
*
* @param source the source to wrap
*/
SendableCameraWrapper(CS_Source source, const private_init&);
/**
* Gets a sendable wrapper object for the given video source, creating the
* wrapper if one does not already exist for the source.
*
* @param source the video source to wrap
* @return a sendable wrapper object for the video source, usable in
* Shuffleboard via ShuffleboardTab::Add() and ShuffleboardLayout::Add()
*/
static SendableCameraWrapper& Wrap(const cs::VideoSource& source);
static SendableCameraWrapper& Wrap(CS_Source source);
void InitSendable(SendableBuilder& builder) override;
private:
std::string m_uri;
};
} // namespace frc

View File

@@ -18,9 +18,17 @@
#include <wpi/StringMap.h>
#include <wpi/Twine.h>
#include "frc/ErrorBase.h"
#include "frc/WPIErrors.h"
#include "frc/shuffleboard/BuiltInLayouts.h"
#include "frc/shuffleboard/LayoutType.h"
#include "frc/shuffleboard/ShuffleboardComponentBase.h"
#include "frc/shuffleboard/ShuffleboardValue.h"
namespace cs {
class VideoSource;
} // namespace cs
namespace frc {
class ComplexWidget;
@@ -31,7 +39,8 @@ class SimpleWidget;
/**
* Common interface for objects that can contain shuffleboard components.
*/
class ShuffleboardContainer : public virtual ShuffleboardValue {
class ShuffleboardContainer : public virtual ShuffleboardValue,
public ErrorBase {
public:
explicit ShuffleboardContainer(const wpi::Twine& title);
@@ -49,12 +58,55 @@ class ShuffleboardContainer : public virtual ShuffleboardValue {
* Gets the layout with the given type and title, creating it if it does not
* already exist at the time this method is called.
*
* @param type the type of the layout, eg "List" or "Grid"
* @param title the title of the layout
* @param title the title of the layout
* @param layoutType the type of the layout, eg "List" or "Grid"
* @return the layout
*/
ShuffleboardLayout& GetLayout(const wpi::Twine& type,
const wpi::Twine& title);
ShuffleboardLayout& GetLayout(const wpi::Twine& title, BuiltInLayouts type);
/**
* Gets the layout with the given type and title, creating it if it does not
* already exist at the time this method is called.
*
* @param title the title of the layout
* @param layoutType the type of the layout, eg "List" or "Grid"
* @return the layout
*/
ShuffleboardLayout& GetLayout(const wpi::Twine& title,
const LayoutType& type);
/**
* Gets the layout with the given type and title, creating it if it does not
* already exist at the time this method is called. Note: this method should
* only be used to use a layout type that is not already built into
* Shuffleboard. To use a layout built into Shuffleboard, use
* {@link #GetLayout(String, LayoutType)} and the layouts in {@link
* BuiltInLayouts}.
*
* @param title the title of the layout
* @param type the type of the layout, eg "List Layout" or "Grid Layout"
* @return the layout
* @see #GetLayout(String, LayoutType)
*/
ShuffleboardLayout& GetLayout(const wpi::Twine& title,
const wpi::Twine& type);
/**
* Gets the already-defined layout in this container with the given title.
*
* <pre>{@code
* Shuffleboard::GetTab("Example Tab")->getLayout("My Layout",
* &BuiltInLayouts.kList);
*
* // Later...
* Shuffleboard::GetTab("Example Tab")->GetLayout("My Layout");
* }</pre>
*
* @param title the title of the layout to get
* @return the layout with the given title
* @throws if no layout has yet been defined with the given title
*/
ShuffleboardLayout& GetLayout(const wpi::Twine& title);
/**
* Adds a widget to this container to display the given sendable.
@@ -67,6 +119,17 @@ class ShuffleboardContainer : public virtual ShuffleboardValue {
*/
ComplexWidget& Add(const wpi::Twine& title, Sendable& sendable);
/**
* Adds a widget to this container to display the given video stream.
*
* @param title the title of the widget
* @param video the video stream to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
*/
ComplexWidget& Add(const wpi::Twine& title, const cs::VideoSource& video);
/**
* Adds a widget to this container to display the given sendable.
*
@@ -78,6 +141,16 @@ class ShuffleboardContainer : public virtual ShuffleboardValue {
*/
ComplexWidget& Add(Sendable& sendable);
/**
* Adds a widget to this container to display the given video stream.
*
* @param video the video to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the same title as the video source
*/
ComplexWidget& Add(const cs::VideoSource& video);
/**
* Adds a widget to this container to display the given data.
*

View File

@@ -9,12 +9,18 @@
#include <wpi/Twine.h>
#include "frc/shuffleboard/BuiltInWidgets.h"
#include "frc/shuffleboard/ShuffleboardComponent.h"
#include "frc/shuffleboard/WidgetType.h"
namespace frc {
class ShuffleboardContainer;
namespace detail {
const char* GetStringForWidgetType(BuiltInWidgets type);
} // namespace detail
/**
* Abstract superclass for widgets.
*
@@ -23,12 +29,11 @@ class ShuffleboardContainer;
* @tparam Derived the self type
*/
template <typename Derived>
class ShuffleboardWidget
: public ShuffleboardComponent<ShuffleboardWidget<Derived>> {
class ShuffleboardWidget : public ShuffleboardComponent<Derived> {
public:
ShuffleboardWidget(ShuffleboardContainer& parent, const wpi::Twine& title)
: ShuffleboardValue(title),
ShuffleboardComponent<ShuffleboardWidget<Derived>>(parent, title) {}
ShuffleboardComponent<Derived>(parent, title) {}
/**
* Sets the type of widget used to display the data. If not set, the default
@@ -36,6 +41,33 @@ class ShuffleboardWidget
*
* @param widgetType the type of the widget used to display the data
* @return this widget object
* @see BuiltInWidgets
*/
Derived& WithWidget(BuiltInWidgets widgetType) {
return WithWidget(detail::GetStringForWidgetType(widgetType));
}
/**
* Sets the type of widget used to display the data. If not set, the default
* widget type will be used.
*
* @param widgetType the type of the widget used to display the data
* @return this widget object
*/
Derived& WithWidget(const WidgetType& widgetType) {
return WithWidget(widgetType.GetWidgetName());
}
/**
* Sets the type of widget used to display the data. If not set, the default
* widget type will be used. This method should only be used to use a widget
* that does not come built into Shuffleboard (i.e. one that comes with a
* custom or thrid-party plugin). To use a widget that is built into
* Shuffleboard, use {@link #withWidget(WidgetType)} and {@link
* BuiltInWidgets}.
*
* @param widgetType the type of the widget used to display the data
* @return this widget object
*/
Derived& WithWidget(const wpi::Twine& widgetType) {
this->SetType(widgetType);

View File

@@ -0,0 +1,37 @@
/*----------------------------------------------------------------------------*/
/* 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 <wpi/StringRef.h>
namespace frc {
/**
* Represents the type of a widget in Shuffleboard. Using this is preferred over
* specifying raw strings, to avoid typos and having to know or look up the
* exact string name for a desired widget.
*
* @see BuiltInWidgets the built-in widget types
*/
class WidgetType {
public:
explicit constexpr WidgetType(const char* widgetName)
: m_widgetName(widgetName) {}
~WidgetType() = default;
/**
* Gets the string type of the widget as defined by that widget in
* Shuffleboard.
*/
wpi::StringRef GetWidgetName() const;
private:
const char* m_widgetName;
};
} // namespace frc

View File

@@ -20,9 +20,11 @@ namespace frc {
* the Smart Dashboard.
* @deprecated Use Sendable directly instead
*/
class WPI_DEPRECATED("use Sendable directly instead") NamedSendable
: public Sendable {
class NamedSendable : public Sendable {
public:
WPI_DEPRECATED("use Sendable directly instead")
NamedSendable() = default;
void SetName(const wpi::Twine& name) override;
std::string GetSubsystem() const override;
void SetSubsystem(const wpi::Twine& subsystem) override;

View File

@@ -83,11 +83,18 @@ TEST(WatchdogTest, SetTimeout) {
TEST(WatchdogTest, IsExpired) {
Watchdog watchdog(0.2, [] {});
EXPECT_FALSE(watchdog.IsExpired());
watchdog.Enable();
EXPECT_FALSE(watchdog.IsExpired());
std::this_thread::sleep_for(std::chrono::milliseconds(300));
EXPECT_TRUE(watchdog.IsExpired());
watchdog.Disable();
EXPECT_TRUE(watchdog.IsExpired());
watchdog.Reset();
EXPECT_FALSE(watchdog.IsExpired());
}
TEST(WatchdogTest, Epochs) {
@@ -118,7 +125,11 @@ TEST(WatchdogTest, Epochs) {
EXPECT_EQ(0u, watchdogCounter) << "Watchdog triggered early";
}
#ifdef __APPLE__
TEST(WatchdogTest, DISABLED_MultiWatchdog) {
#else
TEST(WatchdogTest, MultiWatchdog) {
#endif
uint32_t watchdogCounter1 = 0;
uint32_t watchdogCounter2 = 0;

View File

@@ -33,7 +33,7 @@ class ShuffleboardInstanceTest : public testing::Test {
TEST_F(ShuffleboardInstanceTest, PathFluent) {
auto entry = m_shuffleboardInstance->GetTab("Tab Title")
.GetLayout("List", "List Layout")
.GetLayout("List Layout", "List")
.Add("Data", "string")
.WithWidget("Text View")
.GetEntry();
@@ -45,10 +45,10 @@ TEST_F(ShuffleboardInstanceTest, PathFluent) {
TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) {
auto entry = m_shuffleboardInstance->GetTab("Tab")
.GetLayout("List", "First")
.GetLayout("List", "Second")
.GetLayout("List", "Third")
.GetLayout("List", "Fourth")
.GetLayout("First", "List")
.GetLayout("Second", "List")
.GetLayout("Third", "List")
.GetLayout("Fourth", "List")
.Add("Value", "string")
.GetEntry();
@@ -60,10 +60,10 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsFluent) {
TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) {
ShuffleboardTab& tab = m_shuffleboardInstance->GetTab("Tab");
ShuffleboardLayout& first = tab.GetLayout("List", "First");
ShuffleboardLayout& second = first.GetLayout("List", "Second");
ShuffleboardLayout& third = second.GetLayout("List", "Third");
ShuffleboardLayout& fourth = third.GetLayout("List", "Fourth");
ShuffleboardLayout& first = tab.GetLayout("First", "List");
ShuffleboardLayout& second = first.GetLayout("Second", "List");
ShuffleboardLayout& third = second.GetLayout("Third", "List");
ShuffleboardLayout& fourth = third.GetLayout("Fourth", "List");
SimpleWidget& widget = fourth.Add("Value", "string");
auto entry = widget.GetEntry();
@@ -75,17 +75,17 @@ TEST_F(ShuffleboardInstanceTest, NestedLayoutsOop) {
TEST_F(ShuffleboardInstanceTest, LayoutTypeIsSet) {
std::string layoutType = "Type";
m_shuffleboardInstance->GetTab("Tab").GetLayout(layoutType, "Title");
m_shuffleboardInstance->GetTab("Tab").GetLayout("Title", layoutType);
m_shuffleboardInstance->Update();
nt::NetworkTableEntry entry = m_ntInstance.GetEntry(
"/Shuffleboard/.metadata/Tab/Title/PreferredComponent");
EXPECT_EQ(layoutType, entry.GetString("Not Set")) << "Layout type not set";
}
TEST_F(ShuffleboardInstanceTest, NestedActuatoWidgetsAreDisabled) {
TEST_F(ShuffleboardInstanceTest, NestedActuatorWidgetsAreDisabled) {
MockActuatorSendable sendable("Actuator");
m_shuffleboardInstance->GetTab("Tab")
.GetLayout("Layout", "Title")
.GetLayout("Title", "Type")
.Add(sendable);
auto controllableEntry =
m_ntInstance.GetEntry("/Shuffleboard/Tab/Title/Actuator/.controllable");

View File

@@ -0,0 +1,65 @@
/*----------------------------------------------------------------------------*/
/* 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 <array>
#include <memory>
#include <string>
#include <networktables/NetworkTableEntry.h>
#include <networktables/NetworkTableInstance.h>
#include "frc/commands/InstantCommand.h"
#include "frc/shuffleboard/BuiltInWidgets.h"
#include "frc/shuffleboard/ShuffleboardInstance.h"
#include "frc/shuffleboard/ShuffleboardTab.h"
#include "frc/shuffleboard/ShuffleboardWidget.h"
#include "frc/smartdashboard/Sendable.h"
#include "gtest/gtest.h"
using namespace frc;
class ShuffleboardWidgetTest : public testing::Test {
void SetUp() override {
m_ntInstance = nt::NetworkTableInstance::Create();
m_instance = std::make_unique<detail::ShuffleboardInstance>(m_ntInstance);
m_tab = &(m_instance->GetTab("Tab"));
}
protected:
nt::NetworkTableInstance m_ntInstance;
ShuffleboardTab* m_tab;
std::unique_ptr<detail::ShuffleboardInstance> m_instance;
};
TEST_F(ShuffleboardWidgetTest, UseBuiltInWidget) {
auto entry =
m_tab->Add("Name", "").WithWidget(BuiltInWidgets::kTextView).GetEntry();
EXPECT_EQ("/Shuffleboard/Tab/Name", entry.GetName())
<< "The widget entry has the wrong name";
}
TEST_F(ShuffleboardWidgetTest, WithProperties) {
wpi::StringMap<std::shared_ptr<nt::Value>> properties{
std::make_pair("min", nt::Value::MakeDouble(0)),
std::make_pair("max", nt::Value::MakeDouble(1))};
auto entry =
m_tab->Add("WithProperties", "").WithProperties(properties).GetEntry();
// Update the instance to generate
// the metadata entries for the widget properties
m_instance->Update();
auto propertiesTable = m_ntInstance.GetTable(
"/Shuffleboard/.metadata/Tab/WithProperties/Properties");
EXPECT_EQ("/Shuffleboard/Tab/WithProperties", entry.GetName())
<< "The widget entry has the wrong name";
EXPECT_FLOAT_EQ(0, propertiesTable->GetEntry("min").GetDouble(-1))
<< "The 'min' property should be 0";
EXPECT_FLOAT_EQ(1, propertiesTable->GetEntry("max").GetDouble(-1))
<< "The 'max' property should be 1";
}

View File

@@ -6,7 +6,7 @@
/*----------------------------------------------------------------------------*/
#include <frc/Joystick.h>
#include <frc/Spark.h>
#include <frc/PWMVictorSPX.h>
#include <frc/TimedRobot.h>
#include <frc/drive/DifferentialDrive.h>
@@ -15,8 +15,8 @@
* Runs the motors with arcade steering.
*/
class Robot : public frc::TimedRobot {
frc::Spark m_leftMotor{0};
frc::Spark m_rightMotor{1};
frc::PWMVictorSPX m_leftMotor{0};
frc::PWMVictorSPX m_rightMotor{1};
frc::DifferentialDrive m_robotDrive{m_leftMotor, m_rightMotor};
frc::Joystick m_stick{0};
@@ -27,4 +27,6 @@ class Robot : public frc::TimedRobot {
}
};
#ifndef RUNNING_FRC_TESTS
int main() { return frc::StartRobot<Robot>(); }
#endif

View File

@@ -60,4 +60,6 @@ class Robot : public frc::TimedRobot {
}
};
#ifndef RUNNING_FRC_TESTS
int main() { return frc::StartRobot<Robot>(); }
#endif

View File

@@ -37,4 +37,6 @@ class Robot : public frc::TimedRobot {
frc::PowerDistributionPanel m_pdp;
};
#ifndef RUNNING_FRC_TESTS
int main() { return frc::StartRobot<Robot>(); }
#endif

View File

@@ -80,4 +80,6 @@ class Robot : public frc::TimedRobot {
frc::Encoder m_encoder{1, 2, false, frc::Encoder::k4X};
};
#ifndef RUNNING_FRC_TESTS
int main() { return frc::StartRobot<Robot>(); }
#endif

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