mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Compare commits
66 Commits
v2019.1.1-
...
v2019.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c2f59051 | ||
|
|
d55ca191b8 | ||
|
|
e8b24717c7 | ||
|
|
182758c05b | ||
|
|
74f7ba04b0 | ||
|
|
997d4fdf47 | ||
|
|
76d9e26633 | ||
|
|
a230c814cb | ||
|
|
12cb77cd7c | ||
|
|
8a9822a96b | ||
|
|
a9371a7586 | ||
|
|
6992f5421f | ||
|
|
43696956d2 | ||
|
|
ae3fd5adac | ||
|
|
404666b298 | ||
|
|
1eb4c99d15 | ||
|
|
910b9f3af7 | ||
|
|
09d90b02fb | ||
|
|
0e1f9c2ed2 | ||
|
|
f156a00117 | ||
|
|
4a6087ed56 | ||
|
|
88a09dd13a | ||
|
|
7d19596367 | ||
|
|
bd05dfa1c7 | ||
|
|
05d6660a6b | ||
|
|
1349dd4bd8 | ||
|
|
fdf298b172 | ||
|
|
453a9047e4 | ||
|
|
e97e7a7611 | ||
|
|
308bdbe298 | ||
|
|
f889b45d59 | ||
|
|
444b899a9f | ||
|
|
f121ccff0d | ||
|
|
bc2c932f92 | ||
|
|
6bdd7ce506 | ||
|
|
c12d7729e3 | ||
|
|
3635116049 | ||
|
|
6105873cbe | ||
|
|
80f87ff8ad | ||
|
|
a2368a6199 | ||
|
|
ae3cb6c83b | ||
|
|
f0f196e5b3 | ||
|
|
7c35355d29 | ||
|
|
75cc09a9e4 | ||
|
|
0e2e180635 | ||
|
|
01d1322066 | ||
|
|
ceed1d74dc | ||
|
|
e1bf623997 | ||
|
|
d46ce13ffe | ||
|
|
300eeb330d | ||
|
|
d817001259 | ||
|
|
8ac4b113a5 | ||
|
|
f3864e9abb | ||
|
|
799c3ea8a6 | ||
|
|
8d95c38e39 | ||
|
|
a7f4e29b73 | ||
|
|
b88369f5e8 | ||
|
|
ce6f1d0588 | ||
|
|
f163216a4c | ||
|
|
c449ef1064 | ||
|
|
6593f4346e | ||
|
|
ce1367a115 | ||
|
|
0d7d880261 | ||
|
|
ca2acec88c | ||
|
|
3721463eb3 | ||
|
|
6e8f8be370 |
6
.wpilib/wpilib_preferences.json
Normal file
6
.wpilib/wpilib_preferences.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enableCppIntellisense": true,
|
||||
"currentLanguage": "cpp",
|
||||
"projectYear": "2019",
|
||||
"teamNumber": 0
|
||||
}
|
||||
19
README.md
19
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>{};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, ¶mFlag))) {
|
||||
if (SUCCEEDED(pProcAmp->Get(tagVideoProc, &newValue, ¶mFlag))) {
|
||||
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, ¶mVal,
|
||||
¶mFlag); // 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, ¶mFlag))) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -82,3 +82,4 @@ kResourceType_DigilentDMC60 = 80
|
||||
kResourceType_PWMVictorSPX = 81
|
||||
kResourceType_RevSparkMaxPWM = 82
|
||||
kResourceType_RevSparkMaxCAN = 83
|
||||
kResourceType_ADIS16470 = 84
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,18 +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,
|
||||
@@ -217,16 +197,14 @@ 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 {
|
||||
@@ -256,25 +234,22 @@ 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;
|
||||
@@ -304,15 +279,14 @@ 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -322,25 +296,22 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ class DriverStationSim {
|
||||
HALSIM_SetDriverStationDsAttached(dsAttached);
|
||||
}
|
||||
|
||||
void NotifyNewData() { HALSIM_NotifyDriverStationNewData(); }
|
||||
|
||||
void ResetData() { HALSIM_ResetDriverStationData(); }
|
||||
};
|
||||
} // namespace sim
|
||||
|
||||
@@ -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,18 +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,
|
||||
@@ -211,16 +206,14 @@ 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 {
|
||||
@@ -250,25 +243,22 @@ 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;
|
||||
@@ -298,10 +288,9 @@ 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) {
|
||||
uint32_t now = GetPacketBaseTime();
|
||||
if (now - i->second.lastTimeStamp < static_cast<uint32_t>(periodMs)) {
|
||||
*status = 0;
|
||||
// Read the data from the stored message into the output
|
||||
std::memcpy(data, i->second.data, i->second.length);
|
||||
@@ -316,25 +305,22 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -100,9 +100,8 @@ sourceSets {
|
||||
dev
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs = ['--release', '8']
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,55 +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(); });
|
||||
m_watchdog.SuppressTimeoutMessage(true);
|
||||
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_watchdog.SuppressTimeoutMessage(true);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -67,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -58,10 +58,15 @@ void Watchdog::Thread::Main() {
|
||||
<< "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
|
||||
@@ -155,8 +160,6 @@ void Watchdog::Disable() {
|
||||
auto thr = m_owner->GetThread();
|
||||
if (!thr) return;
|
||||
|
||||
m_isExpired = false;
|
||||
|
||||
thr->m_watchdogs.remove(this);
|
||||
thr->m_cond.notify_all();
|
||||
}
|
||||
|
||||
12
wpilibc/src/main/native/cpp/shuffleboard/LayoutType.cpp
Normal file
12
wpilibc/src/main/native/cpp/shuffleboard/LayoutType.cpp
Normal 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; }
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
12
wpilibc/src/main/native/cpp/shuffleboard/WidgetType.cpp
Normal file
12
wpilibc/src/main/native/cpp/shuffleboard/WidgetType.cpp
Normal 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; }
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "frc/RobotBase.h"
|
||||
|
||||
namespace frc {
|
||||
/** WPILib FileSystem namespace */
|
||||
namespace filesystem {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -60,4 +60,6 @@ class Robot : public frc::TimedRobot {
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() { return frc::StartRobot<Robot>(); }
|
||||
#endif
|
||||
|
||||
@@ -37,4 +37,6 @@ class Robot : public frc::TimedRobot {
|
||||
frc::PowerDistributionPanel m_pdp;
|
||||
};
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() { return frc::StartRobot<Robot>(); }
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,4 +42,6 @@ void Robot::TeleopPeriodic() { frc::Scheduler::GetInstance()->Run(); }
|
||||
|
||||
void Robot::TestPeriodic() {}
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() { return frc::StartRobot<Robot>(); }
|
||||
#endif
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <frc/DigitalInput.h>
|
||||
#include <frc/Spark.h>
|
||||
#include <frc/PWMVictorSPX.h>
|
||||
#include <frc/commands/Subsystem.h>
|
||||
|
||||
/**
|
||||
@@ -46,6 +46,6 @@ class Claw : public frc::Subsystem {
|
||||
void Log();
|
||||
|
||||
private:
|
||||
frc::Spark m_motor{7};
|
||||
frc::PWMVictorSPX m_motor{7};
|
||||
frc::DigitalInput m_contact{5};
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <frc/AnalogGyro.h>
|
||||
#include <frc/AnalogInput.h>
|
||||
#include <frc/Encoder.h>
|
||||
#include <frc/Spark.h>
|
||||
#include <frc/PWMVictorSPX.h>
|
||||
#include <frc/SpeedControllerGroup.h>
|
||||
#include <frc/commands/Subsystem.h>
|
||||
#include <frc/drive/DifferentialDrive.h>
|
||||
@@ -67,12 +67,12 @@ class DriveTrain : public frc::Subsystem {
|
||||
double GetDistanceToObstacle();
|
||||
|
||||
private:
|
||||
frc::Spark m_frontLeft{1};
|
||||
frc::Spark m_rearLeft{2};
|
||||
frc::PWMVictorSPX m_frontLeft{1};
|
||||
frc::PWMVictorSPX m_rearLeft{2};
|
||||
frc::SpeedControllerGroup m_left{m_frontLeft, m_rearLeft};
|
||||
|
||||
frc::Spark m_frontRight{3};
|
||||
frc::Spark m_rearRight{4};
|
||||
frc::PWMVictorSPX m_frontRight{3};
|
||||
frc::PWMVictorSPX m_rearRight{4};
|
||||
frc::SpeedControllerGroup m_right{m_frontRight, m_rearRight};
|
||||
|
||||
frc::DifferentialDrive m_robotDrive{m_left, m_right};
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <frc/AnalogPotentiometer.h>
|
||||
#include <frc/Spark.h>
|
||||
#include <frc/PWMVictorSPX.h>
|
||||
#include <frc/commands/PIDSubsystem.h>
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ class Elevator : public frc::PIDSubsystem {
|
||||
void UsePIDOutput(double d) override;
|
||||
|
||||
private:
|
||||
frc::Spark m_motor{5};
|
||||
frc::PWMVictorSPX m_motor{5};
|
||||
|
||||
// Conversion value of potentiometer varies between the real world and
|
||||
// simulation
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user