[wpilib] Allow SendableCameraWrappers to take arbitrary URLs (#3850)

Useful for adding cameras that are streamed from a coprocessor

Co-authored-by: Peter Johnson <johnson.peter@gmail.com>
Co-authored-by: Sam Carlberg <sam.carlberg@gmail.com>
This commit is contained in:
Peter Johnson
2022-01-01 10:10:37 -08:00
committed by GitHub
parent a3645dea34
commit ecee224e81
6 changed files with 191 additions and 19 deletions

View File

@@ -5,17 +5,19 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.cscore.VideoSource;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
/** A wrapper to make video sources sendable and usable from Shuffleboard. */
public final class SendableCameraWrapper implements Sendable, AutoCloseable {
private static final String kProtocol = "camera_server://";
private static Map<VideoSource, SendableCameraWrapper> m_wrappers = new WeakHashMap<>();
private static Map<String, SendableCameraWrapper> m_wrappers = new WeakHashMap<>();
private final String m_uri;
@@ -26,9 +28,18 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
* @param source the source to wrap
*/
private SendableCameraWrapper(VideoSource source) {
String name = source.getName();
SendableRegistry.add(this, name);
m_uri = kProtocol + name;
this(source.getName());
}
private SendableCameraWrapper(String cameraName) {
SendableRegistry.add(this, cameraName);
m_uri = kProtocol + cameraName;
}
/** Clears all cached wrapper objects. This should only be used in tests. */
@SuppressWarnings("PMD.DefaultPackage")
static void clearWrappers() {
m_wrappers.clear();
}
@Override
@@ -45,7 +56,52 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
* ShuffleboardTab#add(Sendable)} and {@link ShuffleboardLayout#add(Sendable)}
*/
public static SendableCameraWrapper wrap(VideoSource source) {
return m_wrappers.computeIfAbsent(source, SendableCameraWrapper::new);
return m_wrappers.computeIfAbsent(source.getName(), name -> new SendableCameraWrapper(source));
}
/**
* Creates a wrapper for an arbitrary camera stream. The stream URLs <i>must</i> be specified
* using a host resolvable by a program running on a different host (such as a dashboard); prefer
* using static IP addresses (if known) or DHCP identifiers such as {@code "raspberrypi.local"}.
*
* <p>If a wrapper already exists for the given camera, that wrapper is returned and the specified
* URLs are ignored.
*
* @param cameraName the name of the camera. Cannot be null or empty
* @param cameraUrls the URLs with which the camera stream may be accessed. At least one URL must
* be specified
* @return a sendable wrapper object for the video source, usable in Shuffleboard via {@link
* ShuffleboardTab#add(Sendable)} and {@link ShuffleboardLayout#add(Sendable)}
*/
@SuppressWarnings("PMD.CyclomaticComplexity")
public static SendableCameraWrapper wrap(String cameraName, String... cameraUrls) {
if (m_wrappers.containsKey(cameraName)) {
return m_wrappers.get(cameraName);
}
Objects.requireNonNull(cameraName, "cameraName");
Objects.requireNonNull(cameraUrls, "cameraUrls");
if (cameraName.isEmpty()) {
throw new IllegalArgumentException("Camera name not specified");
}
if (cameraUrls.length == 0) {
throw new IllegalArgumentException("No camera URLs specified");
}
for (int i = 0; i < cameraUrls.length; i++) {
Objects.requireNonNull(cameraUrls[i], "Camera URL at index " + i + " was null");
}
String streams = "/CameraPublisher/" + cameraName + "/streams";
if (NetworkTableInstance.getDefault().getEntries(streams, 0).length != 0) {
throw new IllegalStateException(
"A camera is already being streamed with the name '" + cameraName + "'");
}
NetworkTableInstance.getDefault().getEntry(streams).setStringArray(cameraUrls);
SendableCameraWrapper wrapper = new SendableCameraWrapper(cameraName);
m_wrappers.put(cameraName, wrapper);
return wrapper;
}
@Override

View File

@@ -122,6 +122,20 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
*/
SimpleWidget add(String title, Object defaultValue);
/**
* Adds a widget to this container to display a video stream.
*
* @param title the title of the widget
* @param cameraName the name of the streamed camera
* @param cameraUrls the URLs with which the dashboard can access the camera stream
* @return a widget to display the camera stream
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
default ComplexWidget addCamera(String title, String cameraName, String... cameraUrls) {
return add(title, SendableCameraWrapper.wrap(cameraName, cameraUrls));
}
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be