diff --git a/wpilibc/src/main/native/cpp/shuffleboard/SendableCameraWrapper.cpp b/wpilibc/src/main/native/cpp/shuffleboard/SendableCameraWrapper.cpp new file mode 100644 index 0000000000..87d7e9d882 --- /dev/null +++ b/wpilibc/src/main/native/cpp/shuffleboard/SendableCameraWrapper.cpp @@ -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 +#include + +#include "frc/smartdashboard/SendableBuilder.h" + +using namespace frc; + +namespace { +constexpr const char* kProtocol = "camera_server://"; +wpi::DenseMap> wrappers; +} // namespace + +SendableCameraWrapper& SendableCameraWrapper::Wrap( + const cs::VideoSource& source) { + return Wrap(source.GetHandle()); +} + +SendableCameraWrapper& SendableCameraWrapper::Wrap(CS_Source source) { + auto& wrapper = wrappers[static_cast(source)]; + if (!wrapper) + wrapper = std::make_unique(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); +} diff --git a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp index 336a27f94b..4954cff4e9 100644 --- a/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp +++ b/wpilibc/src/main/native/cpp/shuffleboard/ShuffleboardContainer.cpp @@ -11,6 +11,7 @@ #include #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" @@ -62,6 +63,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"; @@ -69,6 +75,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 defaultValue) { CheckTitle(title); diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h b/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h new file mode 100644 index 0000000000..291e64d459 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/shuffleboard/SendableCameraWrapper.h @@ -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 + +#include + +#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 diff --git a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h index 5098fdb398..e53bcfc309 100644 --- a/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h +++ b/wpilibc/src/main/native/include/frc/shuffleboard/ShuffleboardContainer.h @@ -24,6 +24,10 @@ #include "frc/shuffleboard/ShuffleboardComponentBase.h" #include "frc/shuffleboard/ShuffleboardValue.h" +namespace cs { +class VideoSource; +} // namespace cs + namespace frc { class ComplexWidget; @@ -104,6 +108,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. * @@ -115,6 +130,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. * diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SendableCameraWrapper.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SendableCameraWrapper.java new file mode 100644 index 0000000000..7aa93eefe3 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/SendableCameraWrapper.java @@ -0,0 +1,57 @@ +/*----------------------------------------------------------------------------*/ +/* 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. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.wpilibj.shuffleboard; + +import java.util.Map; +import java.util.WeakHashMap; + +import edu.wpi.cscore.VideoSource; +import edu.wpi.first.wpilibj.Sendable; +import edu.wpi.first.wpilibj.SendableBase; +import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder; + +/** + * A wrapper to make video sources sendable and usable from Shuffleboard. + */ +public final class SendableCameraWrapper extends SendableBase { + private static final String kProtocol = "camera_server://"; + + private static Map m_wrappers = new WeakHashMap<>(); + + private final String m_uri; + + /** + * 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 + */ + private SendableCameraWrapper(VideoSource source) { + super(false); + String name = source.getName(); + setName(name); + m_uri = kProtocol + name; + } + + /** + * 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 + * {@link ShuffleboardTab#add(Sendable)} and {@link ShuffleboardLayout#add(Sendable)} + */ + public static SendableCameraWrapper wrap(VideoSource source) { + return m_wrappers.computeIfAbsent(source, SendableCameraWrapper::new); + } + + @Override + public void initSendable(SendableBuilder builder) { + builder.addStringProperty(".ShuffleboardURI", () -> m_uri, null); + } +} diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java index e3ac342998..a322af18df 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/shuffleboard/ShuffleboardContainer.java @@ -10,6 +10,7 @@ package edu.wpi.first.wpilibj.shuffleboard; import java.util.List; import java.util.NoSuchElementException; +import edu.wpi.cscore.VideoSource; import edu.wpi.first.wpilibj.Sendable; /** @@ -76,6 +77,19 @@ public interface ShuffleboardContainer extends ShuffleboardValue { */ ComplexWidget add(String title, Sendable sendable) throws IllegalArgumentException; + /** + * 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 + */ + default ComplexWidget add(String title, VideoSource video) throws IllegalArgumentException { + return add(title, SendableCameraWrapper.wrap(video)); + } + /** * Adds a widget to this container to display the given sendable. * @@ -86,6 +100,18 @@ public interface ShuffleboardContainer extends 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 + */ + default ComplexWidget add(VideoSource video) { + return add(SendableCameraWrapper.wrap(video)); + } + /** * Adds a widget to this container to display the given data. *