mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-01 02:41:48 +00:00
SCRIPT Move java files
This commit is contained in:
committed by
Peter Johnson
parent
7ca1be9bae
commit
c350c5f112
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A vision pipeline is responsible for running a group of OpenCV algorithms to extract data from an
|
||||
* image.
|
||||
*
|
||||
* @see VisionRunner
|
||||
* @see VisionThread
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface VisionPipeline {
|
||||
/**
|
||||
* Processes the image input and sets the result objects. Implementations should make these
|
||||
* objects accessible.
|
||||
*
|
||||
* @param image The image to process.
|
||||
*/
|
||||
void process(Mat image);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
import edu.wpi.first.cameraserver.CameraServerSharedStore;
|
||||
import edu.wpi.first.cscore.CvSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
/**
|
||||
* A vision runner is a convenient wrapper object to make it easy to run vision pipelines from robot
|
||||
* code. The easiest way to use this is to run it in a {@link VisionThread} and use the listener to
|
||||
* take snapshots of the pipeline's outputs.
|
||||
*
|
||||
* @param <P> Vision pipeline type.
|
||||
* @see VisionPipeline
|
||||
* @see VisionThread
|
||||
* @see <a href="package-summary.html">vision</a>
|
||||
*/
|
||||
public class VisionRunner<P extends VisionPipeline> {
|
||||
private final CvSink m_cvSink = new CvSink("VisionRunner CvSink");
|
||||
private final P m_pipeline;
|
||||
private final Mat m_image = new Mat();
|
||||
private final Listener<? super P> m_listener;
|
||||
private volatile boolean m_enabled = true;
|
||||
|
||||
/**
|
||||
* Listener interface for a callback that should run after a pipeline has processed its input.
|
||||
*
|
||||
* @param <P> the type of the pipeline this listener is for
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Listener<P extends VisionPipeline> {
|
||||
/**
|
||||
* Called when the pipeline has run. This shouldn't take much time to run because it will delay
|
||||
* later calls to the pipeline's {@link VisionPipeline#process process} method. Copying the
|
||||
* outputs and code that uses the copies should be <i>synchronized</i> on the same mutex to
|
||||
* prevent multiple threads from reading and writing to the same memory at the same time.
|
||||
*
|
||||
* @param pipeline the vision pipeline that ran
|
||||
*/
|
||||
void copyPipelineOutputs(P pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code videoSource}, send them to the
|
||||
* {@code pipeline}, and call the {@code listener} when the pipeline has finished to alert user
|
||||
* code when it is safe to access the pipeline's outputs.
|
||||
*
|
||||
* @param videoSource the video source to use to supply images for the pipeline
|
||||
* @param pipeline the vision pipeline to run
|
||||
* @param listener a function to call after the pipeline has finished running
|
||||
*/
|
||||
public VisionRunner(VideoSource videoSource, P pipeline, Listener<? super P> listener) {
|
||||
this.m_pipeline = pipeline;
|
||||
this.m_listener = listener;
|
||||
m_cvSink.setSource(videoSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the pipeline one time, giving it the next image from the video source specified in the
|
||||
* constructor. This will block until the source either has an image or throws an error. If the
|
||||
* source successfully supplied a frame, the pipeline's image input will be set, the pipeline will
|
||||
* run, and the listener specified in the constructor will be called to notify it that the
|
||||
* pipeline ran.
|
||||
*
|
||||
* <p>This method is exposed to allow teams to add additional functionality or have their own ways
|
||||
* to run the pipeline. Most teams, however, should just use {@link #runForever} in its own thread
|
||||
* using a {@link VisionThread}.
|
||||
*/
|
||||
public void runOnce() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().threadId() == id) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runOnce() cannot be called from the main robot thread");
|
||||
}
|
||||
runOnceInternal();
|
||||
}
|
||||
|
||||
private void runOnceInternal() {
|
||||
long frameTime = m_cvSink.grabFrame(m_image);
|
||||
if (frameTime == 0) {
|
||||
// There was an error, report it
|
||||
String error = m_cvSink.getError();
|
||||
CameraServerSharedStore.getCameraServerShared().reportDriverStationError(error);
|
||||
} else {
|
||||
// No errors, process the image
|
||||
m_pipeline.process(m_image);
|
||||
m_listener.copyPipelineOutputs(m_pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method that calls {@link #runOnce()} in an infinite loop. This must be run in a
|
||||
* dedicated thread, and cannot be used in the main robot thread because it will freeze the robot
|
||||
* program.
|
||||
*
|
||||
* <p><strong>Do not call this method directly from the main thread.</strong>
|
||||
*
|
||||
* @throws IllegalStateException if this is called from the main robot thread
|
||||
* @see VisionThread
|
||||
*/
|
||||
public void runForever() {
|
||||
Long id = CameraServerSharedStore.getCameraServerShared().getRobotMainThreadId();
|
||||
|
||||
if (id != null && Thread.currentThread().threadId() == id) {
|
||||
throw new IllegalStateException(
|
||||
"VisionRunner.runForever() cannot be called from the main robot thread");
|
||||
}
|
||||
while (m_enabled && !Thread.interrupted()) {
|
||||
runOnceInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop a RunForever() loop. */
|
||||
public void stop() {
|
||||
m_enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.vision;
|
||||
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
|
||||
/**
|
||||
* A vision thread is a special thread that runs a vision pipeline. It is a <i>daemon</i> thread; it
|
||||
* does not prevent the program from exiting when all other non-daemon threads have finished
|
||||
* running.
|
||||
*
|
||||
* @see VisionPipeline
|
||||
* @see VisionRunner
|
||||
* @see Thread#setDaemon(boolean)
|
||||
*/
|
||||
public class VisionThread extends Thread {
|
||||
/**
|
||||
* Creates a vision thread that continuously runs a {@link VisionPipeline}.
|
||||
*
|
||||
* @param visionRunner the runner for a vision pipeline
|
||||
*/
|
||||
@SuppressWarnings("this-escape")
|
||||
public VisionThread(VisionRunner<?> visionRunner) {
|
||||
super(visionRunner::runForever, "WPILib Vision Thread");
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new vision thread that continuously runs the given vision pipeline. This is
|
||||
* equivalent to {@code new VisionThread(new VisionRunner<>(videoSource, pipeline, listener))}.
|
||||
*
|
||||
* @param videoSource the source for images the pipeline should process
|
||||
* @param pipeline the pipeline to run
|
||||
* @param listener the listener to copy outputs from the pipeline after it runs
|
||||
* @param <P> the type of the pipeline
|
||||
*/
|
||||
public <P extends VisionPipeline> VisionThread(
|
||||
VideoSource videoSource, P pipeline, VisionRunner.Listener<? super P> listener) {
|
||||
this(new VisionRunner<>(videoSource, pipeline, listener));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
/**
|
||||
* Classes in the {@code edu.wpi.first.vision} package are designed to simplify using OpenCV vision
|
||||
* processing code from a robot program.
|
||||
*
|
||||
* <p>An example use case for grabbing a yellow tote from 2015 in autonomous: <br>
|
||||
*
|
||||
* <pre><code>
|
||||
* public class Robot extends TimedRobot
|
||||
* implements VisionRunner.Listener<MyFindTotePipeline> {
|
||||
*
|
||||
* // A USB camera connected to the roboRIO.
|
||||
* private {@link edu.wpi.first.cscore.VideoSource VideoSource} usbCamera;
|
||||
*
|
||||
* // A vision pipeline. This could be handwritten or generated by GRIP.
|
||||
* // This has to implement {@link edu.wpi.first.vision.VisionPipeline}.
|
||||
* // For this example, assume that it's perfect and will always see the tote.
|
||||
* private MyFindTotePipeline findTotePipeline;
|
||||
* private {@link edu.wpi.first.vision.VisionThread} findToteThread;
|
||||
*
|
||||
* // The object to synchronize on to make sure the vision thread doesn't
|
||||
* // write to variables the main thread is using.
|
||||
* private final Object visionLock = new Object();
|
||||
*
|
||||
* // The pipeline outputs we want
|
||||
* private boolean pipelineRan = false; // lets us know when the pipeline has actually run
|
||||
* private double angleToTote = 0;
|
||||
* private double distanceToTote = 0;
|
||||
*
|
||||
* public Robot() {
|
||||
* usbCamera = CameraServer.startAutomaticCapture(0);
|
||||
* findTotePipeline = new MyFindTotePipeline();
|
||||
* findToteThread = new VisionThread(usbCamera, findTotePipeline, this);
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void {@link edu.wpi.first.vision.VisionRunner.Listener#copyPipelineOutputs
|
||||
* copyPipelineOutputs(MyFindTotePipeline pipeline)} {
|
||||
* synchronized (visionLock) {
|
||||
* // Take a snapshot of the pipeline's output because
|
||||
* // it may have changed the next time this method is called!
|
||||
* this.pipelineRan = true;
|
||||
* this.angleToTote = pipeline.getAngleToTote();
|
||||
* this.distanceToTote = pipeline.getDistanceToTote();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousInit() {
|
||||
* findToteThread.start();
|
||||
* }
|
||||
*
|
||||
* {@literal @}Override
|
||||
* public void autonomousPeriodic() {
|
||||
* double angle;
|
||||
* double distance;
|
||||
* synchronized (visionLock) {
|
||||
* if (!pipelineRan) {
|
||||
* // Wait until the pipeline has run
|
||||
* return;
|
||||
* }
|
||||
* // Copy the outputs to make sure they're all from the same run
|
||||
* angle = this.angleToTote;
|
||||
* distance = this.distanceToTote;
|
||||
* }
|
||||
* if (!aimedAtTote()) {
|
||||
* turnToAngle(angle);
|
||||
* } else if (!droveToTote()) {
|
||||
* driveDistance(distance);
|
||||
* } else if (!grabbedTote()) {
|
||||
* grabTote();
|
||||
* } else {
|
||||
* // Tote was grabbed and we're done!
|
||||
* return;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </code></pre>
|
||||
*/
|
||||
package edu.wpi.first.vision;
|
||||
@@ -0,0 +1,837 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
import edu.wpi.first.cscore.CameraServerJNI;
|
||||
import edu.wpi.first.cscore.CvSink;
|
||||
import edu.wpi.first.cscore.CvSource;
|
||||
import edu.wpi.first.cscore.MjpegServer;
|
||||
import edu.wpi.first.cscore.UsbCamera;
|
||||
import edu.wpi.first.cscore.VideoEvent;
|
||||
import edu.wpi.first.cscore.VideoException;
|
||||
import edu.wpi.first.cscore.VideoListener;
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import edu.wpi.first.cscore.VideoSink;
|
||||
import edu.wpi.first.cscore.VideoSource;
|
||||
import edu.wpi.first.networktables.BooleanEntry;
|
||||
import edu.wpi.first.networktables.BooleanPublisher;
|
||||
import edu.wpi.first.networktables.IntegerEntry;
|
||||
import edu.wpi.first.networktables.IntegerPublisher;
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import edu.wpi.first.networktables.StringArrayPublisher;
|
||||
import edu.wpi.first.networktables.StringArrayTopic;
|
||||
import edu.wpi.first.networktables.StringEntry;
|
||||
import edu.wpi.first.networktables.StringPublisher;
|
||||
import edu.wpi.first.util.PixelFormat;
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Singleton class for creating and keeping camera servers. Also publishes camera information to
|
||||
* NetworkTables.
|
||||
*/
|
||||
public final class CameraServer {
|
||||
/** CameraServer base port. */
|
||||
public static final int kBasePort = 1181;
|
||||
|
||||
private static final String kPublishName = "/CameraPublisher";
|
||||
|
||||
private static final class PropertyPublisher implements AutoCloseable {
|
||||
@SuppressWarnings("fallthrough")
|
||||
PropertyPublisher(NetworkTable table, VideoEvent event) {
|
||||
String name;
|
||||
String infoName;
|
||||
if (event.name.startsWith("raw_")) {
|
||||
name = "RawProperty/" + event.name;
|
||||
infoName = "RawPropertyInfo/" + event.name;
|
||||
} else {
|
||||
name = "Property/" + event.name;
|
||||
infoName = "PropertyInfo/" + event.name;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean:
|
||||
m_booleanValueEntry = table.getBooleanTopic(name).getEntry(false);
|
||||
m_booleanValueEntry.setDefault(event.value != 0);
|
||||
break;
|
||||
case kEnum:
|
||||
m_choicesTopic = table.getStringArrayTopic(infoName + "/choices");
|
||||
// fallthrough
|
||||
case kInteger:
|
||||
m_integerValueEntry = table.getIntegerTopic(name).getEntry(0);
|
||||
m_minPublisher = table.getIntegerTopic(infoName + "/min").publish();
|
||||
m_maxPublisher = table.getIntegerTopic(infoName + "/max").publish();
|
||||
m_stepPublisher = table.getIntegerTopic(infoName + "/step").publish();
|
||||
m_defaultPublisher = table.getIntegerTopic(infoName + "/default").publish();
|
||||
|
||||
m_integerValueEntry.setDefault(event.value);
|
||||
m_minPublisher.set(CameraServerJNI.getPropertyMin(event.propertyHandle));
|
||||
m_maxPublisher.set(CameraServerJNI.getPropertyMax(event.propertyHandle));
|
||||
m_stepPublisher.set(CameraServerJNI.getPropertyStep(event.propertyHandle));
|
||||
m_defaultPublisher.set(CameraServerJNI.getPropertyDefault(event.propertyHandle));
|
||||
break;
|
||||
case kString:
|
||||
m_stringValueEntry = table.getStringTopic(name).getEntry("");
|
||||
m_stringValueEntry.setDefault(event.valueStr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (VideoException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
void update(VideoEvent event) {
|
||||
switch (event.propertyKind) {
|
||||
case kBoolean -> {
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.set(event.value != 0);
|
||||
}
|
||||
}
|
||||
case kInteger, kEnum -> {
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.set(event.value);
|
||||
}
|
||||
}
|
||||
case kString -> {
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.set(event.valueStr);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (m_booleanValueEntry != null) {
|
||||
m_booleanValueEntry.close();
|
||||
}
|
||||
if (m_integerValueEntry != null) {
|
||||
m_integerValueEntry.close();
|
||||
}
|
||||
if (m_stringValueEntry != null) {
|
||||
m_stringValueEntry.close();
|
||||
}
|
||||
if (m_minPublisher != null) {
|
||||
m_minPublisher.close();
|
||||
}
|
||||
if (m_maxPublisher != null) {
|
||||
m_maxPublisher.close();
|
||||
}
|
||||
if (m_stepPublisher != null) {
|
||||
m_stepPublisher.close();
|
||||
}
|
||||
if (m_defaultPublisher != null) {
|
||||
m_defaultPublisher.close();
|
||||
}
|
||||
if (m_choicesPublisher != null) {
|
||||
m_choicesPublisher.close();
|
||||
}
|
||||
} finally {
|
||||
Reference.reachabilityFence(m_videoListener);
|
||||
}
|
||||
}
|
||||
|
||||
BooleanEntry m_booleanValueEntry;
|
||||
IntegerEntry m_integerValueEntry;
|
||||
StringEntry m_stringValueEntry;
|
||||
IntegerPublisher m_minPublisher;
|
||||
IntegerPublisher m_maxPublisher;
|
||||
IntegerPublisher m_stepPublisher;
|
||||
IntegerPublisher m_defaultPublisher;
|
||||
StringArrayTopic m_choicesTopic;
|
||||
StringArrayPublisher m_choicesPublisher;
|
||||
}
|
||||
|
||||
private static final class SourcePublisher implements AutoCloseable {
|
||||
SourcePublisher(NetworkTable table, int sourceHandle) {
|
||||
this.m_table = table;
|
||||
m_sourcePublisher = table.getStringTopic("source").publish();
|
||||
m_descriptionPublisher = table.getStringTopic("description").publish();
|
||||
m_connectedPublisher = table.getBooleanTopic("connected").publish();
|
||||
m_streamsPublisher = table.getStringArrayTopic("streams").publish();
|
||||
m_modeEntry = table.getStringTopic("mode").getEntry("");
|
||||
m_modesPublisher = table.getStringArrayTopic("modes").publish();
|
||||
|
||||
m_sourcePublisher.set(makeSourceValue(sourceHandle));
|
||||
m_descriptionPublisher.set(CameraServerJNI.getSourceDescription(sourceHandle));
|
||||
m_connectedPublisher.set(CameraServerJNI.isSourceConnected(sourceHandle));
|
||||
m_streamsPublisher.set(getSourceStreamValues(sourceHandle));
|
||||
|
||||
try {
|
||||
VideoMode mode = CameraServerJNI.getSourceVideoMode(sourceHandle);
|
||||
m_modeEntry.setDefault(videoModeToString(mode));
|
||||
m_modesPublisher.set(getSourceModeValues(sourceHandle));
|
||||
} catch (VideoException ignored) {
|
||||
// Do nothing. Let the other event handlers update this if there is an error.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
m_sourcePublisher.close();
|
||||
m_descriptionPublisher.close();
|
||||
m_connectedPublisher.close();
|
||||
m_streamsPublisher.close();
|
||||
m_modeEntry.close();
|
||||
m_modesPublisher.close();
|
||||
for (PropertyPublisher pp : m_properties.values()) {
|
||||
pp.close();
|
||||
}
|
||||
}
|
||||
|
||||
final NetworkTable m_table;
|
||||
final StringPublisher m_sourcePublisher;
|
||||
final StringPublisher m_descriptionPublisher;
|
||||
final BooleanPublisher m_connectedPublisher;
|
||||
final StringArrayPublisher m_streamsPublisher;
|
||||
final StringEntry m_modeEntry;
|
||||
final StringArrayPublisher m_modesPublisher;
|
||||
final Map<Integer, PropertyPublisher> m_properties = new HashMap<>();
|
||||
}
|
||||
|
||||
private static final AtomicInteger m_defaultUsbDevice = new AtomicInteger();
|
||||
private static String m_primarySourceName;
|
||||
private static final Map<String, VideoSource> m_sources = new HashMap<>();
|
||||
private static final Map<String, VideoSink> m_sinks = new HashMap<>();
|
||||
private static final Map<Integer, SourcePublisher> m_publishers =
|
||||
new HashMap<>(); // indexed by source handle
|
||||
// source handle indexed by sink handle
|
||||
private static final Map<Integer, Integer> m_fixedSources = new HashMap<>();
|
||||
private static final NetworkTable m_publishTable =
|
||||
NetworkTableInstance.getDefault().getTable(kPublishName);
|
||||
|
||||
// We publish sources to NetworkTables using the following structure:
|
||||
// "/CameraPublisher/{Source.Name}/" - root
|
||||
// - "source" (string): Descriptive, prefixed with type (e.g. "usb:0")
|
||||
// - "streams" (string array): URLs that can be used to stream data
|
||||
// - "description" (string): Description of the source
|
||||
// - "connected" (boolean): Whether source is connected
|
||||
// - "mode" (string): Current video mode
|
||||
// - "modes" (string array): Available video modes
|
||||
// - "Property/{Property}" - Property values
|
||||
// - "PropertyInfo/{Property}" - Property supporting information
|
||||
|
||||
// Listener for video events
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
private static final VideoListener m_videoListener =
|
||||
new VideoListener(
|
||||
event -> {
|
||||
synchronized (CameraServer.class) {
|
||||
switch (event.kind) {
|
||||
case kSourceCreated -> {
|
||||
// Create subtable for the camera
|
||||
NetworkTable table = m_publishTable.getSubTable(event.name);
|
||||
m_publishers.put(
|
||||
event.sourceHandle, new SourcePublisher(table, event.sourceHandle));
|
||||
}
|
||||
case kSourceDestroyed -> {
|
||||
SourcePublisher publisher = m_publishers.remove(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
try {
|
||||
publisher.close();
|
||||
} catch (Exception e) {
|
||||
// ignore (nothing we can do about it)
|
||||
}
|
||||
}
|
||||
}
|
||||
case kSourceConnected -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
// update the description too (as it may have changed)
|
||||
publisher.m_descriptionPublisher.set(
|
||||
CameraServerJNI.getSourceDescription(event.sourceHandle));
|
||||
publisher.m_connectedPublisher.set(true);
|
||||
}
|
||||
}
|
||||
case kSourceDisconnected -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_connectedPublisher.set(false);
|
||||
}
|
||||
}
|
||||
case kSourceVideoModesUpdated -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modesPublisher.set(getSourceModeValues(event.sourceHandle));
|
||||
}
|
||||
}
|
||||
case kSourceVideoModeChanged -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_modeEntry.set(videoModeToString(event.mode));
|
||||
}
|
||||
}
|
||||
case kSourcePropertyCreated -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
publisher.m_properties.put(
|
||||
event.propertyHandle, new PropertyPublisher(publisher.m_table, event));
|
||||
}
|
||||
}
|
||||
case kSourcePropertyValueUpdated -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null) {
|
||||
pp.update(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
case kSourcePropertyChoicesUpdated -> {
|
||||
SourcePublisher publisher = m_publishers.get(event.sourceHandle);
|
||||
if (publisher != null) {
|
||||
PropertyPublisher pp = publisher.m_properties.get(event.propertyHandle);
|
||||
if (pp != null && pp.m_choicesTopic != null) {
|
||||
try {
|
||||
String[] choices =
|
||||
CameraServerJNI.getEnumPropertyChoices(event.propertyHandle);
|
||||
if (pp.m_choicesPublisher == null) {
|
||||
pp.m_choicesPublisher = pp.m_choicesTopic.publish();
|
||||
}
|
||||
pp.m_choicesPublisher.set(choices);
|
||||
} catch (VideoException ignored) {
|
||||
// ignore (just don't publish choices if we can't get them)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case kSinkSourceChanged,
|
||||
kSinkCreated,
|
||||
kSinkDestroyed,
|
||||
kNetworkInterfacesChanged -> {
|
||||
m_addresses = CameraServerJNI.getNetworkInterfaces();
|
||||
updateStreamValues();
|
||||
}
|
||||
default -> {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
0x4fff,
|
||||
true);
|
||||
|
||||
private static int m_nextPort = kBasePort;
|
||||
private static String[] m_addresses = new String[0];
|
||||
|
||||
/**
|
||||
* Return URI of source with the given index.
|
||||
*
|
||||
* @param source Source index.
|
||||
*/
|
||||
private static String makeSourceValue(int source) {
|
||||
return switch (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))) {
|
||||
case kUsb -> "usb:" + CameraServerJNI.getUsbCameraPath(source);
|
||||
case kHttp -> {
|
||||
String[] urls = CameraServerJNI.getHttpCameraUrls(source);
|
||||
if (urls.length > 0) {
|
||||
yield "ip:" + urls[0];
|
||||
} else {
|
||||
yield "ip:";
|
||||
}
|
||||
}
|
||||
case kCv -> "cv:";
|
||||
case kRaw -> "raw:";
|
||||
case kUnknown -> "unknown:";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URI of stream with the given address and port.
|
||||
*
|
||||
* @param address Stream IP address.
|
||||
* @param port Stream remote port.
|
||||
*/
|
||||
private static String makeStreamValue(String address, int port) {
|
||||
return "mjpg:http://" + address + ":" + port + "/?action=stream";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URI of sink stream with the given index.
|
||||
*
|
||||
* @param sink Sink index.
|
||||
*/
|
||||
private static synchronized String[] getSinkStreamValues(int sink) {
|
||||
// Ignore all but MjpegServer
|
||||
if (VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink)) != VideoSink.Kind.kMjpeg) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// Get port
|
||||
int port = CameraServerJNI.getMjpegServerPort(sink);
|
||||
|
||||
// Generate values
|
||||
ArrayList<String> values = new ArrayList<>(m_addresses.length + 1);
|
||||
String listenAddress = CameraServerJNI.getMjpegServerListenAddress(sink);
|
||||
if (!listenAddress.isEmpty()) {
|
||||
// If a listen address is specified, only use that
|
||||
values.add(makeStreamValue(listenAddress, port));
|
||||
} else {
|
||||
// Otherwise generate for hostname and all interface addresses
|
||||
values.add(makeStreamValue(CameraServerJNI.getHostname() + ".local", port));
|
||||
for (String addr : m_addresses) {
|
||||
if ("127.0.0.1".equals(addr)) {
|
||||
continue; // ignore localhost
|
||||
}
|
||||
values.add(makeStreamValue(addr, port));
|
||||
}
|
||||
}
|
||||
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of stream source URIs for the given source index.
|
||||
*
|
||||
* @param source Source index.
|
||||
*/
|
||||
private static synchronized String[] getSourceStreamValues(int source) {
|
||||
// Ignore all but HttpCamera
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
!= VideoSource.Kind.kHttp) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
// Generate values
|
||||
String[] values = CameraServerJNI.getHttpCameraUrls(source);
|
||||
for (int j = 0; j < values.length; j++) {
|
||||
values[j] = "mjpg:" + values[j];
|
||||
}
|
||||
|
||||
if (CameraServerSharedStore.getCameraServerShared().isRoboRIO()) {
|
||||
// Look to see if we have a passthrough server for this source
|
||||
// Only do this on the roboRIO
|
||||
for (VideoSink i : m_sinks.values()) {
|
||||
int sink = i.getHandle();
|
||||
int sinkSource = CameraServerJNI.getSinkSource(sink);
|
||||
if (source == sinkSource
|
||||
&& VideoSink.getKindFromInt(CameraServerJNI.getSinkKind(sink))
|
||||
== VideoSink.Kind.kMjpeg) {
|
||||
// Add USB-only passthrough
|
||||
String[] finalValues = Arrays.copyOf(values, values.length + 1);
|
||||
int port = CameraServerJNI.getMjpegServerPort(sink);
|
||||
finalValues[values.length] = makeStreamValue("172.22.11.2", port);
|
||||
return finalValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/** Update list of stream URIs. */
|
||||
private static 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 =
|
||||
Objects.requireNonNullElseGet(
|
||||
m_fixedSources.get(sink), () -> CameraServerJNI.getSinkSource(sink));
|
||||
|
||||
if (source == 0) {
|
||||
continue;
|
||||
}
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Don't set stream values if this is a HttpCamera passthrough
|
||||
if (VideoSource.getKindFromInt(CameraServerJNI.getSourceKind(source))
|
||||
== VideoSource.Kind.kHttp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set table value
|
||||
String[] values = getSinkStreamValues(sink);
|
||||
if (values.length > 0) {
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Over all the sources...
|
||||
for (VideoSource i : m_sources.values()) {
|
||||
int source = i.getHandle();
|
||||
|
||||
// Get the source's subtable (if none exists, we're done)
|
||||
SourcePublisher publisher = m_publishers.get(source);
|
||||
if (publisher != null) {
|
||||
// Set table value
|
||||
String[] values = getSourceStreamValues(source);
|
||||
if (values.length > 0) {
|
||||
publisher.m_streamsPublisher.set(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provide string description of pixel format. */
|
||||
private static String pixelFormatToString(PixelFormat pixelFormat) {
|
||||
return switch (pixelFormat) {
|
||||
case kMJPEG -> "MJPEG";
|
||||
case kYUYV -> "YUYV";
|
||||
case kRGB565 -> "RGB565";
|
||||
case kBGR -> "BGR";
|
||||
case kBGRA -> "BGRA";
|
||||
case kGray -> "Gray";
|
||||
case kY16 -> "Y16";
|
||||
case kUYVY -> "UYVY";
|
||||
case kUnknown -> "Unknown";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide string description of video mode.
|
||||
*
|
||||
* <p>The returned string is "{width}x{height} {format} {fps} fps".
|
||||
*/
|
||||
private static String videoModeToString(VideoMode mode) {
|
||||
return mode.width
|
||||
+ "x"
|
||||
+ mode.height
|
||||
+ " "
|
||||
+ pixelFormatToString(mode.pixelFormat)
|
||||
+ " "
|
||||
+ mode.fps
|
||||
+ " fps";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of video modes for the given source handle.
|
||||
*
|
||||
* @param sourceHandle Source handle.
|
||||
*/
|
||||
private static String[] getSourceModeValues(int sourceHandle) {
|
||||
VideoMode[] modes = CameraServerJNI.enumerateSourceVideoModes(sourceHandle);
|
||||
String[] modeStrings = new String[modes.length];
|
||||
for (int i = 0; i < modes.length; i++) {
|
||||
modeStrings[i] = videoModeToString(modes[i]);
|
||||
}
|
||||
return modeStrings;
|
||||
}
|
||||
|
||||
private CameraServer() {}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* <p>You should call this method to see a camera feed on the dashboard. If you also want to
|
||||
* perform vision processing on the roboRIO, use getVideo() to get access to the camera images.
|
||||
*
|
||||
* <p>The first time this overload is called, it calls {@link #startAutomaticCapture(int)} with
|
||||
* device 0, creating a camera named "USB Camera 0". Subsequent calls increment the device number
|
||||
* (e.g. 1, 2, etc).
|
||||
*
|
||||
* @return The USB camera capturing images.
|
||||
*/
|
||||
public static UsbCamera startAutomaticCapture() {
|
||||
return startAutomaticCapture(m_defaultUsbDevice.getAndIncrement());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* <p>This overload calls {@link #startAutomaticCapture(String, int)} with a name of "USB Camera
|
||||
* {dev}".
|
||||
*
|
||||
* @param dev The device number of the camera interface
|
||||
* @return The USB camera capturing images.
|
||||
*/
|
||||
public static UsbCamera startAutomaticCapture(int dev) {
|
||||
UsbCamera camera = new UsbCamera("USB Camera " + dev, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "auto");
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param dev The device number of the camera interface
|
||||
* @return The USB camera capturing images.
|
||||
*/
|
||||
public static UsbCamera startAutomaticCapture(String name, int dev) {
|
||||
UsbCamera camera = new UsbCamera(name, dev);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + dev + "]", "name");
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @param path The device path (e.g. "/dev/video0") of the camera
|
||||
* @return The USB camera capturing images.
|
||||
*/
|
||||
public static UsbCamera startAutomaticCapture(String name, String path) {
|
||||
UsbCamera camera = new UsbCamera(name, path);
|
||||
startAutomaticCapture(camera);
|
||||
CameraServerSharedStore.reportUsage("UsbCamera[" + path + "]", "path");
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start automatically capturing images to send to the dashboard from an existing camera.
|
||||
*
|
||||
* @param camera Camera
|
||||
* @return The MJPEG server serving images from the given camera.
|
||||
*/
|
||||
public static MjpegServer startAutomaticCapture(VideoSource camera) {
|
||||
addCamera(camera);
|
||||
MjpegServer server = addServer("serve_" + camera.getName());
|
||||
server.setSource(camera);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param name The name to give the camera
|
||||
* @return The MJPEG server serving images from the given camera.
|
||||
*/
|
||||
public static MjpegServer addSwitchedCamera(String name) {
|
||||
// create a dummy CvSource
|
||||
CvSource source = new CvSource(name, PixelFormat.kMJPEG, 160, 120, 30);
|
||||
MjpegServer server = startAutomaticCapture(source);
|
||||
synchronized (CameraServer.class) {
|
||||
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.
|
||||
*
|
||||
* <p>This is only valid to call after a camera feed has been added with startAutomaticCapture()
|
||||
* or addServer().
|
||||
*
|
||||
* @return OpenCV sink for the primary camera feed
|
||||
*/
|
||||
public static CvSink getVideo() {
|
||||
VideoSource source;
|
||||
synchronized (CameraServer.class) {
|
||||
if (m_primarySourceName == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
source = m_sources.get(m_primarySourceName);
|
||||
}
|
||||
if (source == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
return getVideo(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the specified camera. This allows you to get images from the camera for
|
||||
* image processing on the roboRIO.
|
||||
*
|
||||
* @param camera Camera (e.g. as returned by startAutomaticCapture).
|
||||
* @return OpenCV sink for the specified camera
|
||||
*/
|
||||
public static CvSink getVideo(VideoSource camera) {
|
||||
String name = "opencv_" + camera.getName();
|
||||
|
||||
synchronized (CameraServer.class) {
|
||||
VideoSink sink = m_sinks.get(name);
|
||||
if (sink != null) {
|
||||
VideoSink.Kind kind = sink.getKind();
|
||||
if (kind != VideoSink.Kind.kCv) {
|
||||
throw new VideoException("expected OpenCV sink, but got " + kind);
|
||||
}
|
||||
return (CvSink) sink;
|
||||
}
|
||||
}
|
||||
|
||||
CvSink newsink = new CvSink(name);
|
||||
newsink.setSource(camera);
|
||||
addServer(newsink);
|
||||
return newsink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the specified camera. This allows you to get images from the camera for
|
||||
* image processing on the roboRIO.
|
||||
*
|
||||
* @param camera Camera (e.g. as returned by startAutomaticCapture).
|
||||
* @param pixelFormat Desired pixelFormat of the camera
|
||||
* @return OpenCV sink for the specified camera
|
||||
*/
|
||||
public static CvSink getVideo(VideoSource camera, PixelFormat pixelFormat) {
|
||||
String name = "opencv_" + camera.getName();
|
||||
|
||||
synchronized (CameraServer.class) {
|
||||
VideoSink sink = m_sinks.get(name);
|
||||
if (sink != null) {
|
||||
VideoSink.Kind kind = sink.getKind();
|
||||
if (kind != VideoSink.Kind.kCv) {
|
||||
throw new VideoException("expected OpenCV sink, but got " + kind);
|
||||
}
|
||||
return (CvSink) sink;
|
||||
}
|
||||
}
|
||||
|
||||
CvSink newsink = new CvSink(name, pixelFormat);
|
||||
newsink.setSource(camera);
|
||||
addServer(newsink);
|
||||
return newsink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenCV access to the specified camera. This allows you to get images from the camera for
|
||||
* image processing on the roboRIO.
|
||||
*
|
||||
* @param name Camera name
|
||||
* @return OpenCV sink for the specified camera
|
||||
*/
|
||||
public static CvSink getVideo(String name) {
|
||||
VideoSource source;
|
||||
synchronized (CameraServer.class) {
|
||||
source = m_sources.get(name);
|
||||
if (source == null) {
|
||||
throw new VideoException("could not find camera " + name);
|
||||
}
|
||||
}
|
||||
return getVideo(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MJPEG stream with OpenCV input. This can be called to pass custom annotated images to
|
||||
* the dashboard.
|
||||
*
|
||||
* @param name Name to give the stream
|
||||
* @param width Width of the image being sent
|
||||
* @param height Height of the image being sent
|
||||
* @return OpenCV source for the MJPEG stream
|
||||
*/
|
||||
public static CvSource putVideo(String name, int width, int height) {
|
||||
CvSource source = new CvSource(name, PixelFormat.kMJPEG, width, height, 30);
|
||||
startAutomaticCapture(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server at the next available port.
|
||||
*
|
||||
* @param name Server name
|
||||
* @return The MJPEG server
|
||||
*/
|
||||
public static MjpegServer addServer(String name) {
|
||||
int port;
|
||||
synchronized (CameraServer.class) {
|
||||
port = m_nextPort;
|
||||
m_nextPort++;
|
||||
}
|
||||
return addServer(name, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a MJPEG server.
|
||||
*
|
||||
* @param name Server name
|
||||
* @param port Server port
|
||||
* @return The MJPEG server
|
||||
*/
|
||||
public static MjpegServer addServer(String name, int port) {
|
||||
MjpegServer server = new MjpegServer(name, port);
|
||||
addServer(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already created server.
|
||||
*
|
||||
* @param server Server
|
||||
*/
|
||||
public static void addServer(VideoSink server) {
|
||||
synchronized (CameraServer.class) {
|
||||
m_sinks.put(server.getName(), server);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a server by name.
|
||||
*
|
||||
* @param name Server name
|
||||
*/
|
||||
public static void removeServer(String name) {
|
||||
synchronized (CameraServer.class) {
|
||||
m_sinks.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server for the primary camera feed.
|
||||
*
|
||||
* <p>This is only valid to call after a camera feed has been added with startAutomaticCapture()
|
||||
* or addServer().
|
||||
*
|
||||
* @return The server for the primary camera feed
|
||||
*/
|
||||
public static VideoSink getServer() {
|
||||
synchronized (CameraServer.class) {
|
||||
if (m_primarySourceName == null) {
|
||||
throw new VideoException("no camera available");
|
||||
}
|
||||
return getServer("serve_" + m_primarySourceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a server by name.
|
||||
*
|
||||
* @param name Server name
|
||||
* @return The server
|
||||
*/
|
||||
public static VideoSink getServer(String name) {
|
||||
synchronized (CameraServer.class) {
|
||||
return m_sinks.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already created camera.
|
||||
*
|
||||
* @param camera Camera
|
||||
*/
|
||||
public static void addCamera(VideoSource camera) {
|
||||
String name = camera.getName();
|
||||
synchronized (CameraServer.class) {
|
||||
if (m_primarySourceName == null) {
|
||||
m_primarySourceName = name;
|
||||
}
|
||||
m_sources.put(name, camera);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a camera by name.
|
||||
*
|
||||
* @param name Camera name
|
||||
*/
|
||||
public static void removeCamera(String name) {
|
||||
synchronized (CameraServer.class) {
|
||||
m_sources.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
/** CameraServer shared functions. */
|
||||
public interface CameraServerShared {
|
||||
/**
|
||||
* get the main thread id func.
|
||||
*
|
||||
* @return the robotMainThreadId
|
||||
*/
|
||||
Long getRobotMainThreadId();
|
||||
|
||||
/**
|
||||
* Report an error to the driver station.
|
||||
*
|
||||
* @param error the error to set
|
||||
*/
|
||||
void reportDriverStationError(String error);
|
||||
|
||||
/**
|
||||
* Report usage.
|
||||
*
|
||||
* @param resource the resource name
|
||||
* @param data arbitrary string data
|
||||
*/
|
||||
void reportUsage(String resource, String data);
|
||||
|
||||
/**
|
||||
* Get if running on a roboRIO.
|
||||
*
|
||||
* @return true if on roboRIO
|
||||
*/
|
||||
default boolean isRoboRIO() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.cameraserver;
|
||||
|
||||
/** Storage for CameraServerShared instance. */
|
||||
public final class CameraServerSharedStore {
|
||||
private static CameraServerShared cameraServerShared;
|
||||
|
||||
private CameraServerSharedStore() {}
|
||||
|
||||
/**
|
||||
* Get the CameraServerShared object.
|
||||
*
|
||||
* @return The CameraServerSharedObject
|
||||
*/
|
||||
public static synchronized CameraServerShared getCameraServerShared() {
|
||||
if (cameraServerShared == null) {
|
||||
cameraServerShared =
|
||||
new CameraServerShared() {
|
||||
@Override
|
||||
public void reportUsage(String resource, String data) {}
|
||||
|
||||
@Override
|
||||
public void reportDriverStationError(String error) {}
|
||||
|
||||
@Override
|
||||
public Long getRobotMainThreadId() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return cameraServerShared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report usage.
|
||||
*
|
||||
* @param resource the resource name
|
||||
* @param data arbitrary string data
|
||||
*/
|
||||
public static void reportUsage(String resource, String data) {
|
||||
getCameraServerShared().reportUsage(resource, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CameraServerShared object.
|
||||
*
|
||||
* @param shared The CameraServerShared object.
|
||||
*/
|
||||
public static synchronized void setCameraServerShared(CameraServerShared shared) {
|
||||
cameraServerShared = shared;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user