[wpilib] Remove Shuffleboard API (#7730)

This commit is contained in:
Peter Johnson
2025-01-24 23:47:42 -08:00
committed by GitHub
parent 01e71e73ce
commit adbe95e610
82 changed files with 60 additions and 6776 deletions

View File

@@ -14,7 +14,6 @@
#include "frc/DSControlWord.h" #include "frc/DSControlWord.h"
#include "frc/Errors.h" #include "frc/Errors.h"
#include "frc/livewindow/LiveWindow.h" #include "frc/livewindow/LiveWindow.h"
#include "frc/shuffleboard/Shuffleboard.h"
#include "frc/smartdashboard/SmartDashboard.h" #include "frc/smartdashboard/SmartDashboard.h"
using namespace frc; using namespace frc;
@@ -153,7 +152,6 @@ void IterativeRobotBase::LoopFunc() {
} else if (m_lastMode == Mode::kTest) { } else if (m_lastMode == Mode::kTest) {
if (m_lwEnabledInTest) { if (m_lwEnabledInTest) {
LiveWindow::SetEnabled(false); LiveWindow::SetEnabled(false);
Shuffleboard::DisableActuatorWidgets();
} }
TestExit(); TestExit();
} }
@@ -171,7 +169,6 @@ void IterativeRobotBase::LoopFunc() {
} else if (mode == Mode::kTest) { } else if (mode == Mode::kTest) {
if (m_lwEnabledInTest) { if (m_lwEnabledInTest) {
LiveWindow::SetEnabled(true); LiveWindow::SetEnabled(true);
Shuffleboard::EnableActuatorWidgets();
} }
TestInit(); TestInit();
m_watchdog.AddEpoch("TestInit()"); m_watchdog.AddEpoch("TestInit()");
@@ -206,8 +203,6 @@ void IterativeRobotBase::LoopFunc() {
m_watchdog.AddEpoch("SmartDashboard::UpdateValues()"); m_watchdog.AddEpoch("SmartDashboard::UpdateValues()");
LiveWindow::UpdateValues(); LiveWindow::UpdateValues();
m_watchdog.AddEpoch("LiveWindow::UpdateValues()"); m_watchdog.AddEpoch("LiveWindow::UpdateValues()");
Shuffleboard::Update();
m_watchdog.AddEpoch("Shuffleboard::Update()");
if constexpr (IsSimulation()) { if constexpr (IsSimulation()) {
HAL_SimPeriodicBefore(); HAL_SimPeriodicBefore();

View File

@@ -1,46 +0,0 @@
// 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.
#include "frc/shuffleboard/ComplexWidget.h"
#include <memory>
#include <wpi/sendable/Sendable.h>
#include "frc/smartdashboard/SendableBuilderImpl.h"
using namespace frc;
ComplexWidget::ComplexWidget(ShuffleboardContainer& parent,
std::string_view title, wpi::Sendable& sendable)
: ShuffleboardValue(title),
ShuffleboardWidget(parent, title),
m_sendable(sendable) {}
ComplexWidget::~ComplexWidget() = default;
void ComplexWidget::EnableIfActuator() {
if (m_builder && static_cast<SendableBuilderImpl&>(*m_builder).IsActuator()) {
static_cast<SendableBuilderImpl&>(*m_builder).StartLiveWindowMode();
}
}
void ComplexWidget::DisableIfActuator() {
if (m_builder && static_cast<SendableBuilderImpl&>(*m_builder).IsActuator()) {
static_cast<SendableBuilderImpl&>(*m_builder).StopLiveWindowMode();
}
}
void ComplexWidget::BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) {
BuildMetadata(metaTable);
if (!m_builder) {
m_builder = std::make_unique<SendableBuilderImpl>();
static_cast<SendableBuilderImpl&>(*m_builder)
.SetTable(parentTable->GetSubTable(GetTitle()));
m_sendable.InitSendable(static_cast<SendableBuilderImpl&>(*m_builder));
static_cast<SendableBuilderImpl&>(*m_builder).StartListeners();
}
m_builder->Update();
}

View File

@@ -1,11 +0,0 @@
// 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.
#include "frc/shuffleboard/LayoutType.h"
using namespace frc;
std::string_view LayoutType::GetLayoutName() const {
return m_layoutName;
}

View File

@@ -1,50 +0,0 @@
// 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.
#include "frc/shuffleboard/RecordingController.h"
#include <string>
#include "frc/Errors.h"
using namespace frc;
using namespace frc::detail;
RecordingController::RecordingController(nt::NetworkTableInstance ntInstance) {
m_recordingControlEntry =
ntInstance.GetBooleanTopic("/Shuffleboard/.recording/RecordData")
.Publish();
m_recordingFileNameFormatEntry =
ntInstance.GetStringTopic("/Shuffleboard/.recording/FileNameFormat")
.Publish();
m_eventsTable = ntInstance.GetTable("/Shuffleboard/.recording/events");
}
void RecordingController::StartRecording() {
m_recordingControlEntry.Set(true);
}
void RecordingController::StopRecording() {
m_recordingControlEntry.Set(false);
}
void RecordingController::SetRecordingFileNameFormat(std::string_view format) {
m_recordingFileNameFormatEntry.Set(format);
}
void RecordingController::ClearRecordingFileNameFormat() {
m_recordingFileNameFormatEntry.Set("");
}
void RecordingController::AddEventMarker(
std::string_view name, std::string_view description,
ShuffleboardEventImportance importance) {
if (name.empty()) {
FRC_ReportError(err::Error, "Shuffleboard event name was not specified");
return;
}
m_eventsTable->GetSubTable(name)->GetEntry("Info").SetStringArray(
{{std::string{description},
std::string{ShuffleboardEventImportanceName(importance)}}});
}

View File

@@ -1,32 +0,0 @@
// 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.
#include "frc/shuffleboard/SendableCameraWrapper.h"
#include <functional>
#include <memory>
#include <string>
#include <wpi/StringMap.h>
#include <wpi/sendable/SendableBuilder.h>
#include <wpi/sendable/SendableRegistry.h>
namespace frc {
namespace detail {
std::shared_ptr<SendableCameraWrapper>& GetSendableCameraWrapper(
std::string_view cameraName) {
static wpi::StringMap<std::shared_ptr<SendableCameraWrapper>> wrappers;
return wrappers[cameraName];
}
void AddToSendableRegistry(wpi::Sendable* sendable, std::string_view name) {
wpi::SendableRegistry::Add(sendable, name);
}
} // namespace detail
void SendableCameraWrapper::InitSendable(wpi::SendableBuilder& builder) {
builder.AddStringProperty(
".ShuffleboardURI", [this] { return m_uri; }, nullptr);
}
} // namespace frc

View File

@@ -1,92 +0,0 @@
// 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.
#include "frc/shuffleboard/Shuffleboard.h"
#include <memory>
#include <networktables/NetworkTableInstance.h>
#include "frc/shuffleboard/ShuffleboardTab.h"
using namespace frc;
void Shuffleboard::Update() {
GetInstance().Update();
}
ShuffleboardTab& Shuffleboard::GetTab(std::string_view title) {
return GetInstance().GetTab(title);
}
void Shuffleboard::SelectTab(int index) {
GetInstance().SelectTab(index);
}
void Shuffleboard::SelectTab(std::string_view title) {
GetInstance().SelectTab(title);
}
void Shuffleboard::EnableActuatorWidgets() {
GetInstance().EnableActuatorWidgets();
}
void Shuffleboard::DisableActuatorWidgets() {
// Need to update to make sure the sendable builders are initialized
Update();
GetInstance().DisableActuatorWidgets();
}
void Shuffleboard::StartRecording() {
GetRecordingController().StartRecording();
}
void Shuffleboard::StopRecording() {
GetRecordingController().StopRecording();
}
void Shuffleboard::SetRecordingFileNameFormat(std::string_view format) {
GetRecordingController().SetRecordingFileNameFormat(format);
}
void Shuffleboard::ClearRecordingFileNameFormat() {
GetRecordingController().ClearRecordingFileNameFormat();
}
void Shuffleboard::AddEventMarker(std::string_view name,
std::string_view description,
ShuffleboardEventImportance importance) {
GetRecordingController().AddEventMarker(name, description, importance);
}
void Shuffleboard::AddEventMarker(std::string_view name,
ShuffleboardEventImportance importance) {
AddEventMarker(name, "", importance);
}
static std::unique_ptr<detail::ShuffleboardInstance>& GetInstanceHolder() {
static std::unique_ptr<detail::ShuffleboardInstance> instance =
std::make_unique<detail::ShuffleboardInstance>(
nt::NetworkTableInstance::GetDefault());
return instance;
}
#ifndef __FRC_ROBORIO__
namespace frc::impl {
void ResetShuffleboardInstance() {
GetInstanceHolder() = std::make_unique<detail::ShuffleboardInstance>(
nt::NetworkTableInstance::GetDefault());
}
} // namespace frc::impl
#endif
detail::ShuffleboardInstance& Shuffleboard::GetInstance() {
return *GetInstanceHolder();
}
detail::RecordingController& Shuffleboard::GetRecordingController() {
static detail::RecordingController inst(
nt::NetworkTableInstance::GetDefault());
return inst;
}

View File

@@ -1,66 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardComponentBase.h"
#include <memory>
#include <string>
using namespace frc;
ShuffleboardComponentBase::ShuffleboardComponentBase(
ShuffleboardContainer& parent, std::string_view title,
std::string_view type)
: ShuffleboardValue(title), m_parent(parent), m_type(type) {}
void ShuffleboardComponentBase::SetType(std::string_view type) {
m_type = type;
m_metadataDirty = true;
}
void ShuffleboardComponentBase::BuildMetadata(
std::shared_ptr<nt::NetworkTable> metaTable) {
if (!m_metadataDirty) {
return;
}
// Component type
if (!GetType().empty()) {
metaTable->GetEntry("PreferredComponent").SetString(GetType());
}
// Tile size
if (m_width > 0 && m_height > 0) {
metaTable->GetEntry("Size").SetDoubleArray(
{{static_cast<double>(m_width), static_cast<double>(m_height)}});
}
// Tile position
if (m_column >= 0 && m_row >= 0) {
metaTable->GetEntry("Position")
.SetDoubleArray(
{{static_cast<double>(m_column), static_cast<double>(m_row)}});
}
// Custom properties
if (GetProperties().size() > 0) {
auto propTable = metaTable->GetSubTable("Properties");
for (auto& entry : GetProperties()) {
propTable->GetEntry(entry.first).SetValue(entry.second);
}
}
m_metadataDirty = false;
}
ShuffleboardContainer& ShuffleboardComponentBase::GetParent() {
return m_parent;
}
const std::string& ShuffleboardComponentBase::GetType() const {
return m_type;
}
const wpi::StringMap<nt::Value>& ShuffleboardComponentBase::GetProperties()
const {
return m_properties;
}

View File

@@ -1,404 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardContainer.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <ntcore_cpp.h>
#include <wpi/sendable/SendableRegistry.h>
#include "frc/Errors.h"
#include "frc/shuffleboard/ComplexWidget.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(std::string_view title)
: ShuffleboardValue(title) {}
const std::vector<std::unique_ptr<ShuffleboardComponentBase>>&
ShuffleboardContainer::GetComponents() const {
return m_components;
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(std::string_view title,
BuiltInLayouts type) {
return GetLayout(title, GetStringFromBuiltInLayout(type));
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(std::string_view title,
const LayoutType& type) {
return GetLayout(title, type.GetLayoutName());
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(std::string_view title,
std::string_view type) {
if (m_layouts.count(title) == 0) {
auto layout = std::make_unique<ShuffleboardLayout>(*this, title, type);
auto ptr = layout.get();
m_components.emplace_back(std::move(layout));
m_layouts.insert(std::pair{title, ptr});
}
return *m_layouts[title];
}
ShuffleboardLayout& ShuffleboardContainer::GetLayout(std::string_view title) {
if (m_layouts.count(title) == 0) {
throw FRC_MakeError(err::InvalidParameter,
"No layout with title {} has been defined", title);
}
return *m_layouts[title];
}
ComplexWidget& ShuffleboardContainer::Add(std::string_view title,
wpi::Sendable& sendable) {
CheckTitle(title);
auto widget = std::make_unique<ComplexWidget>(*this, title, sendable);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
ComplexWidget& ShuffleboardContainer::Add(wpi::Sendable& sendable) {
auto name = wpi::SendableRegistry::GetName(&sendable);
if (name.empty()) {
FRC_ReportError(err::Error, "Sendable must have a name");
}
return Add(name, sendable);
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
const nt::Value& defaultValue) {
CheckTitle(title);
auto widget = std::make_unique<SimpleWidget>(*this, title);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
ptr->GetEntry(nt::GetStringFromType(defaultValue.type()))
->SetDefault(defaultValue);
return *ptr;
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
bool defaultValue) {
return Add(title, nt::Value::MakeBoolean(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
double defaultValue) {
return Add(title, nt::Value::MakeDouble(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
float defaultValue) {
return Add(title, nt::Value::MakeFloat(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
int defaultValue) {
return Add(title, nt::Value::MakeInteger(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
std::string_view defaultValue) {
return Add(title, nt::Value::MakeString(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
const char* defaultValue) {
return Add(title, nt::Value::MakeString(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
std::span<const bool> defaultValue) {
return Add(title, nt::Value::MakeBooleanArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
std::span<const double> defaultValue) {
return Add(title, nt::Value::MakeDoubleArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(std::string_view title,
std::span<const float> defaultValue) {
return Add(title, nt::Value::MakeFloatArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(
std::string_view title, std::span<const int64_t> defaultValue) {
return Add(title, nt::Value::MakeIntegerArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::Add(
std::string_view title, std::span<const std::string> defaultValue) {
return Add(title, nt::Value::MakeStringArray(defaultValue));
}
SuppliedValueWidget<std::string>& ShuffleboardContainer::AddString(
std::string_view title, std::function<std::string()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, std::string value) {
entry.SetString(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::string>>(
*this, title, "string", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<double>& ShuffleboardContainer::AddNumber(
std::string_view title, std::function<double()> supplier) {
return AddDouble(title, std::move(supplier));
}
SuppliedValueWidget<double>& ShuffleboardContainer::AddDouble(
std::string_view title, std::function<double()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, double value) {
entry.SetDouble(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<double>>(
*this, title, "double", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<float>& ShuffleboardContainer::AddFloat(
std::string_view title, std::function<float()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, float value) {
entry.SetFloat(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<float>>(
*this, title, "float", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<int64_t>& ShuffleboardContainer::AddInteger(
std::string_view title, std::function<int64_t()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, int64_t value) {
entry.SetInteger(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<int64_t>>(
*this, title, "int", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<bool>& ShuffleboardContainer::AddBoolean(
std::string_view title, std::function<bool()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, bool value) {
entry.SetBoolean(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<bool>>(
*this, title, "boolean", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<std::string>>&
ShuffleboardContainer::AddStringArray(
std::string_view title,
std::function<std::vector<std::string>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry,
std::vector<std::string> value) {
entry.SetStringArray(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<std::string>>>(
*this, title, "string[]", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<double>>& ShuffleboardContainer::AddNumberArray(
std::string_view title, std::function<std::vector<double>()> supplier) {
return AddDoubleArray(title, std::move(supplier));
}
SuppliedValueWidget<std::vector<double>>& ShuffleboardContainer::AddDoubleArray(
std::string_view title, std::function<std::vector<double>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry,
std::vector<double> value) {
entry.SetDoubleArray(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<double>>>(
*this, title, "double[]", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<float>>& ShuffleboardContainer::AddFloatArray(
std::string_view title, std::function<std::vector<float>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry,
std::vector<float> value) {
entry.SetFloatArray(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<float>>>(
*this, title, "float[]", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<int64_t>>&
ShuffleboardContainer::AddIntegerArray(
std::string_view title, std::function<std::vector<int64_t>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry,
std::vector<int64_t> value) {
entry.SetIntegerArray(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<int64_t>>>(
*this, title, "int[]", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<int>>& ShuffleboardContainer::AddBooleanArray(
std::string_view title, std::function<std::vector<int>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry, std::vector<int> value) {
entry.SetBooleanArray(value);
};
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<int>>>(
*this, title, "boolean[]", supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SuppliedValueWidget<std::vector<uint8_t>>& ShuffleboardContainer::AddRaw(
std::string_view title, std::function<std::vector<uint8_t>()> supplier) {
return AddRaw(title, "raw", std::move(supplier));
}
SuppliedValueWidget<std::vector<uint8_t>>& ShuffleboardContainer::AddRaw(
std::string_view title, std::string_view typeString,
std::function<std::vector<uint8_t>()> supplier) {
static auto setter = [](nt::GenericPublisher& entry,
std::vector<uint8_t> value) { entry.SetRaw(value); };
CheckTitle(title);
auto widget = std::make_unique<SuppliedValueWidget<std::vector<uint8_t>>>(
*this, title, typeString, supplier, setter);
auto ptr = widget.get();
m_components.emplace_back(std::move(widget));
return *ptr;
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, const nt::Value& defaultValue) {
auto& widget = Add(title, defaultValue);
widget.GetEntry(nt::GetStringFromType(defaultValue.type()))
->GetTopic()
.SetPersistent(true);
return widget;
}
SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
bool defaultValue) {
return AddPersistent(title, nt::Value::MakeBoolean(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
double defaultValue) {
return AddPersistent(title, nt::Value::MakeDouble(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
float defaultValue) {
return AddPersistent(title, nt::Value::MakeFloat(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(std::string_view title,
int defaultValue) {
return AddPersistent(title, nt::Value::MakeInteger(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::string_view defaultValue) {
return AddPersistent(title, nt::Value::MakeString(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::span<const bool> defaultValue) {
return AddPersistent(title, nt::Value::MakeBooleanArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::span<const double> defaultValue) {
return AddPersistent(title, nt::Value::MakeDoubleArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::span<const float> defaultValue) {
return AddPersistent(title, nt::Value::MakeFloatArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::span<const int64_t> defaultValue) {
return AddPersistent(title, nt::Value::MakeIntegerArray(defaultValue));
}
SimpleWidget& ShuffleboardContainer::AddPersistent(
std::string_view title, std::span<const std::string> defaultValue) {
return AddPersistent(title, nt::Value::MakeStringArray(defaultValue));
}
void ShuffleboardContainer::EnableIfActuator() {
for (auto& component : GetComponents()) {
component->EnableIfActuator();
}
}
void ShuffleboardContainer::DisableIfActuator() {
for (auto& component : GetComponents()) {
component->DisableIfActuator();
}
}
void ShuffleboardContainer::CheckTitle(std::string_view title) {
std::string titleStr{title};
if (m_usedTitles.count(titleStr) > 0) {
FRC_ReportError(err::Error, "Title is already in use: {}", title);
return;
}
m_usedTitles.insert(titleStr);
}

View File

@@ -1,94 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardInstance.h"
#include <memory>
#include <string>
#include <hal/FRCUsageReporting.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#include <wpi/SmallVector.h>
#include <wpi/StringMap.h>
#include "frc/shuffleboard/Shuffleboard.h"
using namespace frc::detail;
struct ShuffleboardInstance::Impl {
wpi::StringMap<ShuffleboardTab> tabs;
bool tabsChanged = false;
std::shared_ptr<nt::NetworkTable> rootTable;
std::shared_ptr<nt::NetworkTable> rootMetaTable;
nt::StringPublisher selectedTabPub;
};
ShuffleboardInstance::ShuffleboardInstance(nt::NetworkTableInstance ntInstance)
: m_impl(new Impl) {
m_impl->rootTable = ntInstance.GetTable(Shuffleboard::kBaseTableName);
m_impl->rootMetaTable = m_impl->rootTable->GetSubTable(".metadata");
m_impl->selectedTabPub =
m_impl->rootMetaTable->GetStringTopic("Selected")
.Publish(nt::PubSubOptions{.keepDuplicates = true});
}
ShuffleboardInstance::~ShuffleboardInstance() = default;
static bool gReported = false;
frc::ShuffleboardTab& ShuffleboardInstance::GetTab(std::string_view title) {
if (!gReported) {
HAL_Report(HALUsageReporting::kResourceType_Shuffleboard, 0);
gReported = true;
}
auto [it, added] = m_impl->tabs.try_emplace(title, *this, title);
if (added) {
m_impl->tabsChanged = true;
}
return it->second;
}
void ShuffleboardInstance::Update() {
if (m_impl->tabsChanged) {
wpi::SmallVector<std::string, 16> tabTitles;
for (auto& entry : m_impl->tabs) {
tabTitles.emplace_back(entry.second.GetTitle());
}
m_impl->rootMetaTable->GetEntry("Tabs").SetStringArray(tabTitles);
m_impl->tabsChanged = false;
}
for (auto& entry : m_impl->tabs) {
auto& tab = entry.second;
tab.BuildInto(m_impl->rootTable,
m_impl->rootMetaTable->GetSubTable(tab.GetTitle()));
}
}
void ShuffleboardInstance::EnableActuatorWidgets() {
for (auto& entry : m_impl->tabs) {
auto& tab = entry.second;
for (auto& component : tab.GetComponents()) {
component->EnableIfActuator();
}
}
}
void ShuffleboardInstance::DisableActuatorWidgets() {
for (auto& entry : m_impl->tabs) {
auto& tab = entry.second;
for (auto& component : tab.GetComponents()) {
component->DisableIfActuator();
}
}
}
void ShuffleboardInstance::SelectTab(int index) {
m_impl->selectedTabPub.Set(std::to_string(index));
}
void ShuffleboardInstance::SelectTab(std::string_view title) {
m_impl->selectedTabPub.Set(title);
}

View File

@@ -1,35 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardLayout.h"
#include <memory>
#include <wpi/json.h>
using namespace frc;
static constexpr std::string_view kSmartDashboardType = "ShuffleboardLayout";
ShuffleboardLayout::ShuffleboardLayout(ShuffleboardContainer& parent,
std::string_view title,
std::string_view type)
: ShuffleboardValue(title),
ShuffleboardComponent(parent, title, type),
ShuffleboardContainer(title) {
m_isLayout = true;
}
void ShuffleboardLayout::BuildInto(
std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) {
BuildMetadata(metaTable);
auto table = parentTable->GetSubTable(GetTitle());
table->GetEntry(".type").SetString(kSmartDashboardType);
table->GetEntry(".type").GetTopic().SetProperty("SmartDashboard",
kSmartDashboardType);
for (auto& component : GetComponents()) {
component->BuildInto(table, metaTable->GetSubTable(component->GetTitle()));
}
}

View File

@@ -1,32 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardTab.h"
#include <memory>
#include <wpi/json.h>
using namespace frc;
static constexpr std::string_view kSmartDashboardType = "ShuffleboardLayout";
ShuffleboardTab::ShuffleboardTab(ShuffleboardRoot& root, std::string_view title)
: ShuffleboardValue(title), ShuffleboardContainer(title), m_root(root) {}
ShuffleboardRoot& ShuffleboardTab::GetRoot() {
return m_root;
}
void ShuffleboardTab::BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) {
auto tabTable = parentTable->GetSubTable(GetTitle());
tabTable->GetEntry(".type").SetString(kSmartDashboardType);
tabTable->GetEntry(".type").GetTopic().SetProperty("SmartDashboard",
kSmartDashboardType);
for (auto& component : GetComponents()) {
component->BuildInto(tabTable,
metaTable->GetSubTable(component->GetTitle()));
}
}

View File

@@ -1,39 +0,0 @@
// 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.
#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",
"Motor Controller",
"Command",
"PID Command",
"PID Controller",
"Accelerometer",
"3-Axis Accelerometer",
"Gyro",
"Relay",
"Differential Drivebase",
"Mecanum Drivebase",
"Camera Stream",
"Field",
};
const char* detail::GetStringForWidgetType(BuiltInWidgets type) {
return widgetStrings[static_cast<int>(type)];
}

View File

@@ -1,51 +0,0 @@
// 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.
#include "frc/shuffleboard/SimpleWidget.h"
#include <memory>
#include "frc/shuffleboard/Shuffleboard.h"
#include "frc/shuffleboard/ShuffleboardLayout.h"
#include "frc/shuffleboard/ShuffleboardTab.h"
using namespace frc;
SimpleWidget::SimpleWidget(ShuffleboardContainer& parent,
std::string_view title)
: ShuffleboardValue(title), ShuffleboardWidget(parent, title), m_entry() {}
nt::GenericEntry* SimpleWidget::GetEntry() {
if (!m_entry) {
ForceGenerate();
}
return &m_entry;
}
nt::GenericEntry* SimpleWidget::GetEntry(std::string_view typeString) {
if (!m_entry) {
m_typeString = typeString;
ForceGenerate();
}
return &m_entry;
}
void SimpleWidget::BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) {
BuildMetadata(metaTable);
if (!m_entry) {
m_entry = parentTable->GetTopic(GetTitle()).GetGenericEntry(m_typeString);
}
}
void SimpleWidget::ForceGenerate() {
ShuffleboardContainer* parent = &GetParent();
while (parent->m_isLayout) {
parent = &(static_cast<ShuffleboardLayout*>(parent)->GetParent());
}
auto& tab = *static_cast<ShuffleboardTab*>(parent);
tab.GetRoot().Update();
}

View File

@@ -1,11 +0,0 @@
// 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.
#include "frc/shuffleboard/WidgetType.h"
using namespace frc;
std::string_view WidgetType::GetWidgetName() const {
return m_widgetName;
}

View File

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

View File

@@ -1,385 +0,0 @@
// 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.
#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>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 PowerDistribution. <br>Supported types: <ul> <li>
* PowerDistribution</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>
*/
kPowerDistribution,
/**
* Displays a SendableChooser with a dropdown combo box with a list of
* options.
* <br>Supported types:
* <ul>
* <li>SendableChooser</li>
* </ul>
* <br>This widget has no custom properties.
*/
kComboBoxChooser,
/**
* Displays a SendableChooserwith a toggle button for each available option.
* <br>Supported types:
* <ul>
* <li>SendableChooser</li>
* </ul>
* <br>This widget has no custom properties.
*/
kSplitButtonChooser,
/**
* Displays an Encoder displaying its speed,
* total traveled distance, and its distance per tick. <br>Supported types:
* <ul>
* <li>Encoder</li>
* </ul>
* <br>This widget has no custom properties.
*/
kEncoder,
/**
* Displays a MotorController.
* The motor controller will be controllable from the dashboard when test mode
* is enabled, but will otherwise be view-only. <br>Supported types: <ul>
* <li>PWMMotorController</li>
* <li>DMC60</li>
* <li>Jaguar</li>
* <li>PWMTalonSRX</li>
* <li>PWMVictorSPX</li>
* <li>SD540</li>
* <li>Spark</li>
* <li>Talon</li>
* <li>Victor</li>
* <li>VictorSP</li>
* <li>MotorControllerGroup</li>
* <li>Any custom subclass of {@code SpeedContorller}</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>
*/
kMotorController,
/**
* 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>Command</li> <li>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>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>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>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'
* acceleration. <br>Supported types: <ul> <li>ADXL345_I2C</li> <li>
* ADXL345_SPI</li> <li>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>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>ADXRS450_Gyro</li>
* <li>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>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>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>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>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,
/**
* Displays a field2d object.<br>
* Supported types:
*
* <ul>
* <li>Field2d
* </ul>
*/
kField,
};
} // namespace frc

View File

@@ -1,46 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string_view>
#include <networktables/NetworkTable.h>
#include "frc/shuffleboard/ShuffleboardWidget.h"
namespace wpi {
class Sendable;
class SendableBuilder;
} // namespace wpi
namespace frc {
class ShuffleboardContainer;
/**
* A Shuffleboard widget that handles a Sendable object such as a motor
* controller or sensor.
*/
class ComplexWidget final : public ShuffleboardWidget<ComplexWidget> {
public:
ComplexWidget(ShuffleboardContainer& parent, std::string_view title,
wpi::Sendable& sendable);
~ComplexWidget() override;
void EnableIfActuator() override;
void DisableIfActuator() override;
void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) override;
private:
wpi::Sendable& m_sendable;
std::unique_ptr<wpi::SendableBuilder> m_builder;
};
} // namespace frc

View File

@@ -1,34 +0,0 @@
// 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.
#pragma once
#include <string_view>
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.
*/
std::string_view GetLayoutName() const;
private:
const char* m_layoutName;
};
} // namespace frc

View File

@@ -1,40 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <networktables/BooleanTopic.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/StringTopic.h>
#include <wpi/SmallVector.h>
#include "frc/shuffleboard/ShuffleboardEventImportance.h"
namespace frc::detail {
class RecordingController final {
public:
explicit RecordingController(nt::NetworkTableInstance ntInstance);
virtual ~RecordingController() = default;
void StartRecording();
void StopRecording();
void SetRecordingFileNameFormat(std::string_view format);
void ClearRecordingFileNameFormat();
void AddEventMarker(std::string_view name, std::string_view description,
ShuffleboardEventImportance importance);
private:
nt::BooleanPublisher m_recordingControlEntry;
nt::StringPublisher m_recordingFileNameFormatEntry;
std::shared_ptr<nt::NetworkTable> m_eventsTable;
};
} // namespace frc::detail

View File

@@ -1,136 +0,0 @@
// 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.
#pragma once
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#ifndef DYNAMIC_CAMERA_SERVER
#include <cscore_oo.h>
#include <fmt/format.h>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableInstance.h>
#else
namespace cs {
class VideoSource;
} // namespace cs
using CS_Handle = int; // NOLINT
using CS_Source = CS_Handle; // NOLINT
#endif
#include <networktables/StringArrayTopic.h>
#include <wpi/sendable/Sendable.h>
#include <wpi/sendable/SendableHelper.h>
namespace frc {
class SendableCameraWrapper;
namespace detail {
constexpr const char* kProtocol = "camera_server://";
std::shared_ptr<SendableCameraWrapper>& GetSendableCameraWrapper(
std::string_view cameraName);
void AddToSendableRegistry(wpi::Sendable* sendable, std::string_view name);
} // namespace detail
/**
* A wrapper to make video sources sendable and usable from Shuffleboard.
*/
class SendableCameraWrapper
: public wpi::Sendable,
public wpi::SendableHelper<SendableCameraWrapper> {
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 cameraName the name of the camera to wrap
*/
SendableCameraWrapper(std::string_view cameraName, const private_init&);
/**
* Creates a new sendable wrapper. Private constructor to avoid direct
* instantiation with multiple wrappers floating around for the same camera.
*
* @param cameraName the name of the camera to wrap
* @param cameraUrls camera URLs
*/
SendableCameraWrapper(std::string_view cameraName,
std::span<const std::string> cameraUrls,
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);
static SendableCameraWrapper& Wrap(std::string_view cameraName,
std::span<const std::string> cameraUrls);
void InitSendable(wpi::SendableBuilder& builder) override;
private:
std::string m_uri;
nt::StringArrayPublisher m_streams;
};
#ifndef DYNAMIC_CAMERA_SERVER
inline SendableCameraWrapper::SendableCameraWrapper(std::string_view name,
const private_init&)
: m_uri(detail::kProtocol) {
detail::AddToSendableRegistry(this, name);
m_uri += name;
}
inline SendableCameraWrapper::SendableCameraWrapper(
std::string_view cameraName, std::span<const std::string> cameraUrls,
const private_init&)
: SendableCameraWrapper(cameraName, private_init{}) {
m_streams = nt::NetworkTableInstance::GetDefault()
.GetStringArrayTopic(
fmt::format("/CameraPublisher/{}/streams", cameraName))
.Publish();
m_streams.Set(cameraUrls);
}
inline SendableCameraWrapper& SendableCameraWrapper::Wrap(
const cs::VideoSource& source) {
return Wrap(source.GetHandle());
}
inline SendableCameraWrapper& SendableCameraWrapper::Wrap(CS_Source source) {
CS_Status status = 0;
auto name = cs::GetSourceName(source, &status);
auto& wrapper = detail::GetSendableCameraWrapper(name);
if (!wrapper) {
wrapper = std::make_shared<SendableCameraWrapper>(name, private_init{});
}
return *wrapper;
}
inline SendableCameraWrapper& SendableCameraWrapper::Wrap(
std::string_view cameraName, std::span<const std::string> cameraUrls) {
auto& wrapper = detail::GetSendableCameraWrapper(cameraName);
if (!wrapper) {
wrapper = std::make_shared<SendableCameraWrapper>(cameraName, cameraUrls,
private_init{});
}
return *wrapper;
}
#endif
} // namespace frc

View File

@@ -1,196 +0,0 @@
// 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.
#pragma once
#include <string_view>
#include "frc/shuffleboard/RecordingController.h"
#include "frc/shuffleboard/ShuffleboardEventImportance.h"
#include "frc/shuffleboard/ShuffleboardInstance.h"
namespace frc {
class ShuffleboardTab;
/**
* The Shuffleboard class provides a mechanism with which data can be added and
* laid out in the Shuffleboard dashboard application from a robot program. Tabs
* and layouts can be specified, as well as choosing which widgets to display
* with and setting properties of these widgets; for example, programmers can
* specify a specific {@code boolean} value to be displayed with a toggle button
* instead of the default colored box, or set custom colors for that box.
*
* For example, displaying a boolean entry with a toggle button:
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
*
* Changing the colors of the boolean box:
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Boolean Box")
* .withProperties(Map.of("colorWhenTrue", "green", "colorWhenFalse",
* "maroon")) .getEntry();
* }</pre>
*
* Specifying a parent layout. Note that the layout type must <i>always</i> be
* specified, even if the layout has already been generated by a previously
* defined entry.
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .getLayout("List", "Example List")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
* </p>
*
* Teams are encouraged to set up shuffleboard layouts at the start of the robot
* program.
*/
class Shuffleboard final {
public:
/**
* The name of the base NetworkTable into which all Shuffleboard data will be
* added.
*/
static constexpr const char* kBaseTableName = "/Shuffleboard";
/**
* Updates all the values in Shuffleboard. Iterative and timed robots are
* pre-configured to call this method in the main robot loop; teams using
* custom robot base classes, or subclass SampleRobot, should make sure to
* call this repeatedly to keep data on the dashboard up to date.
*/
static void Update();
/**
* Gets the Shuffleboard tab with the given title, creating it if it does not
* already exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
static ShuffleboardTab& GetTab(std::string_view title);
/**
* Selects the tab in the dashboard with the given index in the range
* [0..n-1], where <i>n</i> is the number of tabs in the dashboard at the time
* this method is called.
*
* @param index the index of the tab to select
*/
static void SelectTab(int index);
/**
* Selects the tab in the dashboard with the given title.
*
* @param title the title of the tab to select
*/
static void SelectTab(std::string_view title);
/**
* Enables user control of widgets containing actuators: motor controllers,
* relays, etc. This should only be used when the robot is in test mode.
* IterativeRobotBase and SampleRobot are both configured to call this method
* when entering test mode; most users should not need to use this method
* directly.
*/
static void EnableActuatorWidgets();
/**
* Disables user control of widgets containing actuators. For safety reasons,
* actuators should only be controlled while in test mode. IterativeRobotBase
* and SampleRobot are both configured to call this method when exiting in
* test mode; most users should not need to use this method directly.
*/
static void DisableActuatorWidgets();
/**
* Starts data recording on the dashboard. Has no effect if recording is
* already in progress.
*/
static void StartRecording();
/**
* Stops data recording on the dashboard. Has no effect if no recording is in
* progress.
*/
static void StopRecording();
/**
* Sets the file name format for new recording files to use. If recording is
* in progress when this method is called, it will continue to use the same
* file. New recordings will use the format.
*
* <p>To avoid recording files overwriting each other, make sure to use unique
* recording file names. File name formats accept templates for inserting the
* date and time when the recording started with the {@code ${date}} and
* {@code ${time}} templates, respectively. For example, the default format is
* {@code "recording-${time}"} and recording files created with it will have
* names like {@code "recording-2018.01.15.sbr"}. Users are
* <strong>strongly</strong> recommended to use the {@code ${time}} template
* to ensure unique file names.
* </p>
*
* @param format the format for the
*/
static void SetRecordingFileNameFormat(std::string_view format);
/**
* Clears the custom name format for recording files. New recordings will use
* the default format.
*
* @see SetRecordingFileNameFormat(std::string_view)
*/
static void ClearRecordingFileNameFormat();
/**
* Notifies Shuffleboard of an event. Events can range from as trivial as a
* change in a command state to as critical as a total power loss or component
* failure. If Shuffleboard is recording, the event will also be recorded.
*
* <p>If {@code name} is {@code null} or empty, no event will be sent and an
* error will be printed to the driver station.
*
* @param name the name of the event
* @param description a description of the event
* @param importance the importance of the event
*/
static void AddEventMarker(std::string_view name,
std::string_view description,
ShuffleboardEventImportance importance);
/**
* Notifies Shuffleboard of an event. Events can range from as trivial as a
* change in a command state to as critical as a total power loss or component
* failure. If Shuffleboard is recording, the event will also be recorded.
*
* <p>If {@code name} is {@code null} or empty, no event will be sent and an
* error will be printed to the driver station.
*
* @param name the name of the event
* @param importance the importance of the event
*/
static void AddEventMarker(std::string_view name,
ShuffleboardEventImportance importance);
private:
static detail::ShuffleboardInstance& GetInstance();
static detail::RecordingController& GetRecordingController();
// TODO usage reporting
Shuffleboard() = default;
};
} // namespace frc
// Make use of references returned by member functions usable
#include "frc/shuffleboard/ShuffleboardTab.h"

View File

@@ -1,89 +0,0 @@
// 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.
#pragma once
#include <string_view>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableValue.h>
#include <wpi/StringMap.h>
#include "frc/shuffleboard/ShuffleboardComponentBase.h"
namespace frc {
class ShuffleboardContainer;
/**
* A generic component in Shuffleboard.
*
* @tparam Derived the self type
*/
template <typename Derived>
class ShuffleboardComponent : public ShuffleboardComponentBase {
public:
/**
* Constructs a ShuffleboardComponent.
*
* @param parent The parent container.
* @param title The component title.
* @param type The component type.
*/
ShuffleboardComponent(ShuffleboardContainer& parent, std::string_view title,
std::string_view type = "")
: ShuffleboardValue(title),
ShuffleboardComponentBase(parent, title, type) {}
/**
* Sets custom properties for this component. Property names are
* case-sensitive and whitespace-insensitive (capitalization and spaces do not
* matter).
*
* @param properties the properties for this component
* @return this component
*/
Derived& WithProperties(const wpi::StringMap<nt::Value>& properties) {
m_properties = properties;
m_metadataDirty = true;
return *static_cast<Derived*>(this);
}
/**
* Sets the position of this component in the tab. This has no effect if this
* component is inside a layout.
*
* If the position of a single component is set, it is recommended to set the
* positions of <i>all</i> components inside a tab to prevent Shuffleboard
* from automatically placing another component there before the one with the
* specific position is sent.
*
* @param columnIndex the column in the tab to place this component
* @param rowIndex the row in the tab to place this component
* @return this component
*/
Derived& WithPosition(int columnIndex, int rowIndex) {
m_column = columnIndex;
m_row = rowIndex;
m_metadataDirty = true;
return *static_cast<Derived*>(this);
}
/**
* Sets the size of this component in the tab. This has no effect if this
* component is inside a layout.
*
* @param width how many columns wide the component should be
* @param height how many rows high the component should be
* @return this component
*/
Derived& WithSize(int width, int height) {
m_width = width;
m_height = height;
m_metadataDirty = true;
return *static_cast<Derived*>(this);
}
};
} // namespace frc

View File

@@ -1,55 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <networktables/NetworkTable.h>
#include <networktables/NetworkTableValue.h>
#include <wpi/StringMap.h>
#include "frc/shuffleboard/ShuffleboardValue.h"
namespace frc {
class ShuffleboardContainer;
/**
* A shim class to allow storing ShuffleboardComponents in arrays.
*/
class ShuffleboardComponentBase : public virtual ShuffleboardValue {
public:
ShuffleboardComponentBase(ShuffleboardContainer& parent,
std::string_view title, std::string_view type = "");
void SetType(std::string_view type);
void BuildMetadata(std::shared_ptr<nt::NetworkTable> metaTable);
ShuffleboardContainer& GetParent();
const std::string& GetType() const;
protected:
wpi::StringMap<nt::Value> m_properties;
bool m_metadataDirty = true;
int m_column = -1;
int m_row = -1;
int m_width = -1;
int m_height = -1;
private:
ShuffleboardContainer& m_parent;
std::string m_type;
/**
* Gets the custom properties for this component. May be null.
*/
const wpi::StringMap<nt::Value>& GetProperties() const;
};
} // namespace frc

View File

@@ -1,727 +0,0 @@
// 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.
#pragma once
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <networktables/NetworkTableEntry.h>
#include <networktables/NetworkTableValue.h>
#include <wpi/SmallSet.h>
#include <wpi/StringMap.h>
#include "frc/shuffleboard/BuiltInLayouts.h"
#include "frc/shuffleboard/LayoutType.h"
#include "frc/shuffleboard/ShuffleboardComponentBase.h"
#include "frc/shuffleboard/ShuffleboardValue.h"
#include "frc/shuffleboard/SuppliedValueWidget.h"
namespace cs {
class VideoSource;
} // namespace cs
namespace wpi {
class Sendable;
} // namespace wpi
namespace frc {
class ComplexWidget;
class ShuffleboardLayout;
class SimpleWidget;
/**
* Common interface for objects that can contain shuffleboard components.
*/
class ShuffleboardContainer : public virtual ShuffleboardValue {
public:
explicit ShuffleboardContainer(std::string_view title);
ShuffleboardContainer(ShuffleboardContainer&& rhs) = default;
~ShuffleboardContainer() override = default;
/**
* Gets the components that are direct children of this container.
*/
const std::vector<std::unique_ptr<ShuffleboardComponentBase>>& GetComponents()
const;
/**
* 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 type the type of the layout, eg "List" or "Grid"
* @return the layout
*/
ShuffleboardLayout& GetLayout(std::string_view 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 type the type of the layout, eg "List" or "Grid"
* @return the layout
*/
ShuffleboardLayout& GetLayout(std::string_view 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
* GetLayout(std::string_view, const LayoutType&) and the layouts in
* 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(std::string_view, const LayoutType&)
*/
ShuffleboardLayout& GetLayout(std::string_view title, std::string_view 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(std::string_view title);
/**
* Adds a widget to this container to display the given sendable.
*
* @param title the title of the widget
* @param sendable the sendable 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(std::string_view title, wpi::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(std::string_view title, const cs::VideoSource& video);
/**
* 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
*/
ComplexWidget& AddCamera(std::string_view title, std::string_view cameraName,
std::span<const std::string> cameraUrls);
/**
* Adds a widget to this container to display the given sendable.
*
* @param sendable the sendable to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title, or if the sendable's name has not been
* specified
*/
ComplexWidget& Add(wpi::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.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::shared_ptr<nt::Value>)
* Add(std::string_view title, std::shared_ptr<nt::Value> defaultValue)
*/
SimpleWidget& Add(std::string_view title, const nt::Value& defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, bool)
* Add(std::string_view title, bool defaultValue)
*/
SimpleWidget& Add(std::string_view title, bool defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, double)
* Add(std::string_view title, double defaultValue)
*/
SimpleWidget& Add(std::string_view title, double defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, double)
* Add(std::string_view title, double defaultValue)
*/
SimpleWidget& Add(std::string_view title, float defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, int)
* Add(std::string_view title, int defaultValue)
*/
SimpleWidget& Add(std::string_view title, int defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::string_view)
* Add(std::string_view title, std::string_view defaultValue)
*/
SimpleWidget& Add(std::string_view title, std::string_view defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, const char*)
* Add(std::string_view title, const char* defaultValue)
*/
SimpleWidget& Add(std::string_view title, const char* defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::span<const bool>)
* Add(std::string_view title, std::span<const bool> defaultValue)
*/
SimpleWidget& Add(std::string_view title, std::span<const bool> defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::span<const double>)
* Add(std::string_view title, std::span<const double> defaultValue)
*/
SimpleWidget& Add(std::string_view title,
std::span<const double> defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::span<const double>)
* Add(std::string_view title, std::span<const double> defaultValue)
*/
SimpleWidget& Add(std::string_view title,
std::span<const float> defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::span<const double>)
* Add(std::string_view title, std::span<const double> defaultValue)
*/
SimpleWidget& Add(std::string_view title,
std::span<const int64_t> defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this
* container with the given title
* @see AddPersistent(std::string_view, std::span<const std::string>)
* Add(std::string_view title, std::span<const std::string> defaultValue)
*/
SimpleWidget& Add(std::string_view title,
std::span<const std::string> defaultValue);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::string>& AddString(
std::string_view title, std::function<std::string()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<double>& AddNumber(std::string_view title,
std::function<double()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<double>& AddDouble(std::string_view title,
std::function<double()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<float>& AddFloat(std::string_view title,
std::function<float()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<int64_t>& AddInteger(std::string_view title,
std::function<int64_t()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<bool>& AddBoolean(std::string_view title,
std::function<bool()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<std::string>>& AddStringArray(
std::string_view title,
std::function<std::vector<std::string>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<double>>& AddNumberArray(
std::string_view title, std::function<std::vector<double>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<double>>& AddDoubleArray(
std::string_view title, std::function<std::vector<double>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<float>>& AddFloatArray(
std::string_view title, std::function<std::vector<float>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<int64_t>>& AddIntegerArray(
std::string_view title, std::function<std::vector<int64_t>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<int>>& AddBooleanArray(
std::string_view title, std::function<std::vector<int>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<uint8_t>>& AddRaw(
std::string_view title, std::function<std::vector<uint8_t>()> supplier);
/**
* 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 overridden by values from the value
* supplier.
*
* @param title the title of the widget
* @param typeString the NT type string
* @param supplier the supplier for values
* @return a widget to display data
*/
SuppliedValueWidget<std::vector<uint8_t>>& AddRaw(
std::string_view title, std::string_view typeString,
std::function<std::vector<uint8_t>()> supplier);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::shared_ptr<nt::Value>), the value in the
* widget will be saved on the robot and will be used when the robot program
* next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(stdd::string_view, std::shared_ptr<nt::Value>)
* Add(std::string_view title, std::shared_ptr<nt::Value> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
const nt::Value& defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, bool), the value in the widget will be saved
* on the robot and will be used when the robot program next starts rather
* than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, bool)
* Add(std::string_view title, bool defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title, bool defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, double), the value in the widget will be saved
* on the robot and will be used when the robot program next starts rather
* than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, double)
* Add(std::string_view title, double defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title, double defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, float), the value in the widget will be saved
* on the robot and will be used when the robot program next starts rather
* than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, float)
* Add(std::string_view title, float defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title, float defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, int64_t), the value in the widget will be
* saved on the robot and will be used when the robot program next starts
* rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std:string_view, int64_t)
* Add(std::string_view title, int64_t defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title, int defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::string_view), the value in the widget
* will be saved on the robot and will be used when the robot program next
* starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::string_view)
* Add(std::string_view title, std::string_view defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::string_view defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::span<const bool>), the value in the
* widget will be saved on the robot and will be used when the robot program
* next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::span<const bool>)
* Add(std::string_view title, std::span<const bool> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::span<const bool> defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::span<const double>), the value in the
* widget will be saved on the robot and will be used when the robot program
* next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::span<const double>)
* Add(std::string_view title, std::span<const double> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::span<const double> defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::span<const float>), the value in the
* widget will be saved on the robot and will be used when the robot program
* next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::span<const float>)
* Add(std::string_view title, std::span<const float> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::span<const float> defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::span<const int64_t>), the value in the
* widget will be saved on the robot and will be used when the robot program
* next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::span<const int64_t>)
* Add(std::string_view title, std::span<const int64_t> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::span<const int64_t> defaultValue);
/**
* Adds a widget to this container to display a simple piece of data.
*
* Unlike Add(std::string_view, std::span<const std::string>), the value in
* the widget will be saved on the robot and will be used when the robot
* program next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @see Add(std::string_view, std::span<const std::string>)
* Add(std::string_view title, std::span<const std::string> defaultValue)
*/
SimpleWidget& AddPersistent(std::string_view title,
std::span<const std::string> defaultValue);
void EnableIfActuator() override;
void DisableIfActuator() override;
protected:
bool m_isLayout = false;
private:
wpi::SmallSet<std::string, 32> m_usedTitles;
std::vector<std::unique_ptr<ShuffleboardComponentBase>> m_components;
wpi::StringMap<ShuffleboardLayout*> m_layouts;
/**
* Adds title to internal set if it hasn't already.
*
* @return True if title isn't in use; false otherwise.
*/
void CheckTitle(std::string_view title);
friend class SimpleWidget;
};
} // namespace frc
// Make use of references returned by member functions usable
#include "frc/shuffleboard/ComplexWidget.h"
#include "frc/shuffleboard/ShuffleboardLayout.h"
#include "frc/shuffleboard/SimpleWidget.h"
#ifndef DYNAMIC_CAMERA_SERVER
#include "frc/shuffleboard/SendableCameraWrapper.h"
inline frc::ComplexWidget& frc::ShuffleboardContainer::Add(
const cs::VideoSource& video) {
return Add(frc::SendableCameraWrapper::Wrap(video));
}
inline frc::ComplexWidget& frc::ShuffleboardContainer::Add(
std::string_view title, const cs::VideoSource& video) {
return Add(title, frc::SendableCameraWrapper::Wrap(video));
}
inline frc::ComplexWidget& frc::ShuffleboardContainer::AddCamera(
std::string_view title, std::string_view cameraName,
std::span<const std::string> cameraUrls) {
return Add(title, frc::SendableCameraWrapper::Wrap(cameraName, cameraUrls));
}
#endif

View File

@@ -1,40 +0,0 @@
// 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.
#pragma once
#include <string_view>
namespace frc {
// Maintainer note: this enum is mirrored in WPILibJ and in Shuffleboard
// Modifying the enum or enum strings requires a corresponding change to the
// Java enum and the enum in Shuffleboard
enum ShuffleboardEventImportance { kTrivial, kLow, kNormal, kHigh, kCritical };
/**
* Returns name of the given enum.
*
* @return Name of the given enum.
*/
inline std::string_view ShuffleboardEventImportanceName(
ShuffleboardEventImportance importance) {
switch (importance) {
case kTrivial:
return "TRIVIAL";
case kLow:
return "LOW";
case kNormal:
return "NORMAL";
case kHigh:
return "HIGH";
case kCritical:
return "CRITICAL";
default:
return "NORMAL";
}
}
} // namespace frc

View File

@@ -1,40 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string_view>
#include "frc/shuffleboard/ShuffleboardRoot.h"
#include "frc/shuffleboard/ShuffleboardTab.h"
namespace frc::detail {
class ShuffleboardInstance final : public ShuffleboardRoot {
public:
explicit ShuffleboardInstance(nt::NetworkTableInstance ntInstance);
virtual ~ShuffleboardInstance();
ShuffleboardInstance(ShuffleboardInstance&&) = default;
ShuffleboardInstance& operator=(ShuffleboardInstance&&) = default;
frc::ShuffleboardTab& GetTab(std::string_view title) override;
void Update() override;
void EnableActuatorWidgets() override;
void DisableActuatorWidgets() override;
void SelectTab(int index) override;
void SelectTab(std::string_view) override;
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace frc::detail

View File

@@ -1,40 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string_view>
#include <networktables/NetworkTable.h>
#include "frc/shuffleboard/ShuffleboardComponent.h"
#include "frc/shuffleboard/ShuffleboardContainer.h"
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4250)
#endif
namespace frc {
/**
* A layout in a Shuffleboard tab. Layouts can contain widgets and other
* layouts.
*/
class ShuffleboardLayout : public ShuffleboardComponent<ShuffleboardLayout>,
public ShuffleboardContainer {
public:
ShuffleboardLayout(ShuffleboardContainer& parent, std::string_view name,
std::string_view type);
void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) override;
};
} // namespace frc
#ifdef _WIN32
#pragma warning(pop)
#endif

View File

@@ -1,63 +0,0 @@
// 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.
#pragma once
#include <string_view>
namespace frc {
class ShuffleboardTab;
/**
* The root of the data placed in Shuffleboard. It contains the tabs, but no
* data is placed directly in the root.
*
* This class is package-private to minimize API surface area.
*/
class ShuffleboardRoot {
public:
/**
* Gets the tab with the given title, creating it if it does not already
* exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
virtual ShuffleboardTab& GetTab(std::string_view title) = 0;
/**
* Updates all tabs.
*/
virtual void Update() = 0;
/**
* Enables all widgets in Shuffleboard that offer user control over actuators.
*/
virtual void EnableActuatorWidgets() = 0;
/**
* Disables all widgets in Shuffleboard that offer user control over
* actuators.
*/
virtual void DisableActuatorWidgets() = 0;
/**
* Selects the tab in the dashboard with the given index in the range
* [0..n-1], where <i>n</i> is the number of tabs in the dashboard at the time
* this method is called.
*
* @param index the index of the tab to select
*/
virtual void SelectTab(int index) = 0;
/**
* Selects the tab in the dashboard with the given title.
*
* @param title the title of the tab to select
*/
virtual void SelectTab(std::string_view title) = 0;
};
} // namespace frc

View File

@@ -1,39 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string_view>
#include <networktables/NetworkTable.h>
#include "frc/shuffleboard/ShuffleboardContainer.h"
namespace frc {
class ShuffleboardRoot;
/**
* Represents a tab in the Shuffleboard dashboard. Widgets can be added to the
* tab with Add(Sendable), Add(std::string_view, Object), and
* Add(String, Sendable). Widgets can also be added to layouts with
* GetLayout(std::string_view, std::string_view); layouts can be nested
* arbitrarily deep (note that too many levels may make deeper components
* unusable).
*/
class ShuffleboardTab final : public ShuffleboardContainer {
public:
ShuffleboardTab(ShuffleboardRoot& root, std::string_view title);
ShuffleboardRoot& GetRoot();
void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) override;
private:
ShuffleboardRoot& m_root;
};
} // namespace frc

View File

@@ -1,67 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <networktables/NetworkTable.h>
namespace frc {
class ShuffleboardValue {
public:
explicit ShuffleboardValue(std::string_view title) : m_title(title) {}
virtual ~ShuffleboardValue() = default;
ShuffleboardValue(const ShuffleboardValue&) = delete;
ShuffleboardValue& operator=(const ShuffleboardValue&) = delete;
/**
* Gets the title of this Shuffleboard value.
*/
const std::string& GetTitle() const { return m_title; }
/**
* Builds the entries for this value.
*
* @param parentTable The table containing all the data for the parent. Values
* that require a complex entry or table structure should
* call {@code parentTable.getSubtable(getTitle())} to get
* the table to put data into. Values that only use a
* single entry should call
* {@code parentTable.getEntry(getTitle())} to get that
* entry.
* @param metaTable The table containing all the metadata for this value and
* its sub-values
*/
virtual void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) = 0;
/**
* Enables user control of this widget in the Shuffleboard application.
*
* This method is package-private to prevent users from enabling control
* themselves. Has no effect if the sendable is not marked as an actuator with
* SendableBuilder::SetActuator().
*/
virtual void EnableIfActuator() {}
/**
* Disables user control of this widget in the Shuffleboard application.
*
* This method is package-private to prevent users from enabling control
* themselves. Has no effect if the sendable is not marked as an actuator with
* SendableBuilder::SetActuator().
*/
virtual void DisableIfActuator() {}
private:
std::string m_title;
};
} // namespace frc

View File

@@ -1,74 +0,0 @@
// 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.
#pragma once
#include <string_view>
#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.
*
* <p>This class is package-private to minimize API surface area.
*
* @tparam Derived the self type
*/
template <typename Derived>
class ShuffleboardWidget : public ShuffleboardComponent<Derived> {
public:
ShuffleboardWidget(ShuffleboardContainer& parent, std::string_view title)
: ShuffleboardValue(title),
ShuffleboardComponent<Derived>(parent, title) {}
/**
* 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
* @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 third-party plugin). To use a widget that is built into
* Shuffleboard, use WithWidget(WidgetType) and BuiltInWidgets.
*
* @param widgetType the type of the widget used to display the data
* @return this widget object
*/
Derived& WithWidget(std::string_view widgetType) {
this->SetType(widgetType);
return *static_cast<Derived*>(this);
}
};
} // namespace frc

View File

@@ -1,54 +0,0 @@
// 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.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <networktables/GenericEntry.h>
#include <networktables/NetworkTable.h>
#include "frc/shuffleboard/ShuffleboardWidget.h"
namespace frc {
class ShuffleboardContainer;
/**
* A Shuffleboard widget that handles a single data point such as a number or
* string.
*/
class SimpleWidget final : public ShuffleboardWidget<SimpleWidget> {
public:
SimpleWidget(ShuffleboardContainer& parent, std::string_view title);
/**
* Gets the NetworkTable entry that contains the data for this widget.
* The widget owns the entry; the returned pointer's lifetime is the same as
* that of the widget.
*/
nt::GenericEntry* GetEntry();
/**
* Gets the NetworkTable entry that contains the data for this widget.
* The widget owns the entry; the returned pointer's lifetime is the same as
* that of the widget.
*
* @param typeString NT type string
*/
nt::GenericEntry* GetEntry(std::string_view typeString);
void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) override;
private:
nt::GenericEntry m_entry;
std::string m_typeString;
void ForceGenerate();
};
} // namespace frc

View File

@@ -1,58 +0,0 @@
// 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.
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <networktables/BooleanTopic.h>
#include <networktables/GenericEntry.h>
#include <networktables/NetworkTable.h>
#include "frc/shuffleboard/ShuffleboardComponent.h"
#include "frc/shuffleboard/ShuffleboardComponentBase.h"
#include "frc/shuffleboard/ShuffleboardWidget.h"
namespace frc {
class ShuffleboardContainer;
template <typename T>
class SuppliedValueWidget : public ShuffleboardWidget<SuppliedValueWidget<T>> {
public:
SuppliedValueWidget(ShuffleboardContainer& parent, std::string_view title,
std::string_view typeString, std::function<T()> supplier,
std::function<void(nt::GenericPublisher&, T)> setter)
: ShuffleboardValue(title),
ShuffleboardWidget<SuppliedValueWidget<T>>(parent, title),
m_typeString(typeString),
m_supplier(supplier),
m_setter(setter) {}
void BuildInto(std::shared_ptr<nt::NetworkTable> parentTable,
std::shared_ptr<nt::NetworkTable> metaTable) override {
this->BuildMetadata(metaTable);
if (!m_controllablePub) {
m_controllablePub =
nt::BooleanTopic{metaTable->GetTopic("Controllable")}.Publish();
m_controllablePub.Set(false);
}
if (!m_entry) {
m_entry =
parentTable->GetTopic(this->GetTitle()).GenericPublish(m_typeString);
}
m_setter(m_entry, m_supplier());
}
private:
std::string m_typeString;
std::function<T()> m_supplier;
std::function<void(nt::GenericPublisher&, T)> m_setter;
nt::BooleanPublisher m_controllablePub;
nt::GenericPublisher m_entry;
};
} // namespace frc

View File

@@ -1,34 +0,0 @@
// 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.
#pragma once
#include <string_view>
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.
*/
std::string_view GetWidgetName() const;
private:
const char* m_widgetName;
};
} // namespace frc

View File

@@ -1,16 +0,0 @@
// 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.
#include "shuffleboard/MockActuatorSendable.h"
#include <wpi/sendable/SendableBuilder.h>
#include <wpi/sendable/SendableRegistry.h>
MockActuatorSendable::MockActuatorSendable(std::string_view name) {
wpi::SendableRegistry::Add(this, name);
}
void MockActuatorSendable::InitSendable(wpi::SendableBuilder& builder) {
builder.SetActuator(true);
}

View File

@@ -1,130 +0,0 @@
// 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.
#include "frc/shuffleboard/ShuffleboardInstance.h" // NOLINT(build/include_order)
#include <string_view>
#include <gtest/gtest.h>
#include <networktables/NetworkTableInstance.h>
#include <networktables/NetworkTableListener.h>
#include <networktables/StringTopic.h>
#include "shuffleboard/MockActuatorSendable.h"
class NTWrapper {
public:
NTWrapper() { inst = nt::NetworkTableInstance::Create(); }
~NTWrapper() { nt::NetworkTableInstance::Destroy(inst); }
nt::NetworkTableInstance inst;
};
TEST(ShuffleboardInstanceTest, PathFluent) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
auto entry = shuffleboardInst.GetTab("Tab Title")
.GetLayout("List", "List Layout")
.Add("Data", "string")
.WithWidget("Text View")
.GetEntry();
EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
EXPECT_EQ("/Shuffleboard/Tab Title/List/Data", entry->GetTopic().GetName())
<< "Entry path generated incorrectly";
}
TEST(ShuffleboardInstanceTest, NestedLayoutsFluent) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
auto entry = shuffleboardInst.GetTab("Tab")
.GetLayout("First", "List")
.GetLayout("Second", "List")
.GetLayout("Third", "List")
.GetLayout("Fourth", "List")
.Add("Value", "string")
.GetEntry();
EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
entry->GetTopic().GetName())
<< "Entry path generated incorrectly";
}
TEST(ShuffleboardInstanceTest, NestedLayoutsOop) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
frc::ShuffleboardTab& tab = shuffleboardInst.GetTab("Tab");
frc::ShuffleboardLayout& first = tab.GetLayout("First", "List");
frc::ShuffleboardLayout& second = first.GetLayout("Second", "List");
frc::ShuffleboardLayout& third = second.GetLayout("Third", "List");
frc::ShuffleboardLayout& fourth = third.GetLayout("Fourth", "List");
frc::SimpleWidget& widget = fourth.Add("Value", "string");
auto entry = widget.GetEntry();
EXPECT_EQ("string", entry->GetString("")) << "Wrong entry value";
EXPECT_EQ("/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
entry->GetTopic().GetName())
<< "Entry path generated incorrectly";
}
TEST(ShuffleboardInstanceTest, LayoutTypeIsSet) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
std::string_view layoutType = "Type";
shuffleboardInst.GetTab("Tab").GetLayout("Title", layoutType);
shuffleboardInst.Update();
auto entry = ntInst.inst.GetEntry(
"/Shuffleboard/.metadata/Tab/Title/PreferredComponent");
EXPECT_EQ(layoutType, entry.GetString("Not Set")) << "Layout type not set";
}
TEST(ShuffleboardInstanceTest, NestedActuatorWidgetsAreDisabled) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
MockActuatorSendable sendable("Actuator");
shuffleboardInst.GetTab("Tab").GetLayout("Title", "Layout").Add(sendable);
auto controllableEntry =
ntInst.inst.GetEntry("/Shuffleboard/Tab/Title/Actuator/.controllable");
shuffleboardInst.Update();
// Note: we use the unsafe `GetBoolean()` method because if the value is NOT
// a boolean, or if it is not present, then something has clearly gone very,
// very wrong
bool controllable = controllableEntry.GetValue().GetBoolean();
// Sanity check
EXPECT_TRUE(controllable)
<< "The nested actuator widget should be enabled by default";
shuffleboardInst.DisableActuatorWidgets();
controllable = controllableEntry.GetValue().GetBoolean();
EXPECT_FALSE(controllable)
<< "The nested actuator widget should have been disabled";
}
TEST(ShuffleboardInstanceTest, DuplicateSelectTabs) {
NTWrapper ntInst;
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
std::atomic_int counter = 0;
auto subscriber =
ntInst.inst.GetStringTopic("/Shuffleboard/.metadata/Selected")
.Subscribe("", {.keepDuplicates = true});
ntInst.inst.AddListener(
subscriber, nt::EventFlags::kValueAll | nt::EventFlags::kImmediate,
[&counter](auto& event) { counter++; });
// There shouldn't be anything there
EXPECT_EQ(0, counter);
shuffleboardInst.SelectTab("tab1");
shuffleboardInst.SelectTab("tab1");
EXPECT_TRUE(ntInst.inst.WaitForListenerQueue(1.0))
<< "Listener queue timed out!";
EXPECT_EQ(2, counter);
}

View File

@@ -1,13 +0,0 @@
// 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.
#include <gtest/gtest.h>
#include "frc/shuffleboard/Shuffleboard.h"
TEST(ShuffleboardTest, TabObjectsCached) {
auto& tab1 = frc::Shuffleboard::GetTab("testTabObjectsCached");
auto& tab2 = frc::Shuffleboard::GetTab("testTabObjectsCached");
EXPECT_EQ(&tab1, &tab2) << "Tab objects were not cached";
}

View File

@@ -1,110 +0,0 @@
// 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.
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <networktables/NetworkTableEntry.h>
#include <networktables/NetworkTableInstance.h>
#include "frc/shuffleboard/ShuffleboardInstance.h"
#include "frc/shuffleboard/ShuffleboardTab.h"
using namespace frc;
class NTWrapper {
public:
NTWrapper() { inst = nt::NetworkTableInstance::Create(); }
~NTWrapper() { nt::NetworkTableInstance::Destroy(inst); }
nt::NetworkTableInstance inst;
};
class SuppliedValueWidgetTest : public testing::Test {
protected:
NTWrapper m_ntInst;
frc::detail::ShuffleboardInstance m_shuffleboardInst{m_ntInst.inst};
frc::ShuffleboardTab* m_tab = &(m_shuffleboardInst.GetTab("Tab"));
};
TEST_F(SuppliedValueWidgetTest, AddString) {
std::string str = "foo";
m_tab->AddString("String", [&str]() { return str; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/String");
m_shuffleboardInst.Update();
EXPECT_EQ("foo", entry.GetValue().GetString());
}
TEST_F(SuppliedValueWidgetTest, AddNumber) {
int num = 0;
m_tab->AddNumber("Num", [&num]() { return ++num; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Num");
m_shuffleboardInst.Update();
EXPECT_FLOAT_EQ(1.0, entry.GetValue().GetDouble());
}
TEST_F(SuppliedValueWidgetTest, AddBoolean) {
bool value = true;
m_tab->AddBoolean("Bool", [&value]() { return value; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Bool");
m_shuffleboardInst.Update();
EXPECT_EQ(true, entry.GetValue().GetBoolean());
}
TEST_F(SuppliedValueWidgetTest, AddStringArray) {
std::vector<std::string> strings = {"foo", "bar"};
m_tab->AddStringArray("Strings", [&strings]() { return strings; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Strings");
m_shuffleboardInst.Update();
auto actual = entry.GetValue().GetStringArray();
EXPECT_EQ(strings.size(), actual.size());
for (size_t i = 0; i < strings.size(); i++) {
EXPECT_EQ(strings[i], actual[i]);
}
}
TEST_F(SuppliedValueWidgetTest, AddNumberArray) {
std::vector<double> nums = {0, 1, 2, 3};
m_tab->AddNumberArray("Numbers", [&nums]() { return nums; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Numbers");
m_shuffleboardInst.Update();
auto actual = entry.GetValue().GetDoubleArray();
EXPECT_EQ(nums.size(), actual.size());
for (size_t i = 0; i < nums.size(); i++) {
EXPECT_FLOAT_EQ(nums[i], actual[i]);
}
}
TEST_F(SuppliedValueWidgetTest, AddBooleanArray) {
std::vector<int> bools = {true, false};
m_tab->AddBooleanArray("Booleans", [&bools]() { return bools; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Booleans");
m_shuffleboardInst.Update();
auto actual = entry.GetValue().GetBooleanArray();
EXPECT_EQ(bools.size(), actual.size());
for (size_t i = 0; i < bools.size(); i++) {
EXPECT_FLOAT_EQ(bools[i], actual[i]);
}
}
TEST_F(SuppliedValueWidgetTest, AddRaw) {
std::vector<uint8_t> bytes = {1, 2, 3};
m_tab->AddRaw("Raw", [&bytes]() { return bytes; });
auto entry = m_ntInst.inst.GetEntry("/Shuffleboard/Tab/Raw");
m_shuffleboardInst.Update();
auto actual = entry.GetValue().GetRaw();
EXPECT_EQ(bytes, std::vector<uint8_t>(actual.begin(), actual.end()));
}

View File

@@ -1,19 +0,0 @@
// 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.
#pragma once
#include <string_view>
#include <wpi/sendable/Sendable.h>
/**
* A mock sendable that marks itself as an actuator.
*/
class MockActuatorSendable : public wpi::Sendable {
public:
explicit MockActuatorSendable(std::string_view name);
void InitSendable(wpi::SendableBuilder& builder) override;
};

View File

@@ -4,7 +4,7 @@
#include "RobotContainer.h" #include "RobotContainer.h"
#include <frc/shuffleboard/Shuffleboard.h> #include <frc/smartdashboard/SmartDashboard.h>
RobotContainer::RobotContainer() { RobotContainer::RobotContainer() {
// Initialize all of your commands and subsystems here // Initialize all of your commands and subsystems here
@@ -15,36 +15,10 @@ RobotContainer::RobotContainer() {
m_chooser.AddOption("Complex Auto", m_complexAuto.get()); m_chooser.AddOption("Complex Auto", m_complexAuto.get());
// Put the chooser on the dashboard // Put the chooser on the dashboard
frc::Shuffleboard::GetTab("Autonomous").Add(m_chooser); frc::SmartDashboard::PutData("Autonomous", &m_chooser);
// Put subsystems to dashboard. // Put subsystems to dashboard.
frc::Shuffleboard::GetTab("Drivetrain").Add(m_drive); frc::SmartDashboard::PutData("Drivetrain", &m_drive);
frc::Shuffleboard::GetTab("HatchSubsystem").Add(m_hatch); frc::SmartDashboard::PutData("HatchSubsystem", &m_hatch);
// Log Shuffleboard events for command initialize, execute, finish, interrupt
frc2::CommandScheduler::GetInstance().OnCommandInitialize(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command initialized", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandExecute(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command executed", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandFinish(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command finished", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandInterrupt(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command interrupted", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
// Configure the button bindings // Configure the button bindings
ConfigureButtonBindings(); ConfigureButtonBindings();

View File

@@ -4,7 +4,7 @@
#include "RobotContainer.h" #include "RobotContainer.h"
#include <frc/shuffleboard/Shuffleboard.h> #include <frc/smartdashboard/SmartDashboard.h>
#include <frc2/command/button/JoystickButton.h> #include <frc2/command/button/JoystickButton.h>
#include "commands/DefaultDrive.h" #include "commands/DefaultDrive.h"
@@ -20,36 +20,10 @@ RobotContainer::RobotContainer() {
m_chooser.AddOption("Complex Auto", &m_complexAuto); m_chooser.AddOption("Complex Auto", &m_complexAuto);
// Put the chooser on the dashboard // Put the chooser on the dashboard
frc::Shuffleboard::GetTab("Autonomous").Add(m_chooser); frc::SmartDashboard::PutData("Autonomous", &m_chooser);
// Put subsystems to dashboard. // Put subsystems to dashboard.
frc::Shuffleboard::GetTab("Drivetrain").Add(m_drive); frc::SmartDashboard::PutData("Drivetrain", &m_drive);
frc::Shuffleboard::GetTab("HatchSubsystem").Add(m_hatch); frc::SmartDashboard::PutData("HatchSubsystem", &m_hatch);
// Log Shuffleboard events for command initialize, execute, finish, interrupt
frc2::CommandScheduler::GetInstance().OnCommandInitialize(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command initialized", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandExecute(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command executed", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandFinish(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command finished", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
frc2::CommandScheduler::GetInstance().OnCommandInterrupt(
[](const frc2::Command& command) {
frc::Shuffleboard::AddEventMarker(
"Command interrupted", command.GetName(),
frc::ShuffleboardEventImportance::kNormal);
});
// Configure the button bindings // Configure the button bindings
ConfigureButtonBindings(); ConfigureButtonBindings();

View File

@@ -8,7 +8,6 @@
#include <frc/controller/PIDController.h> #include <frc/controller/PIDController.h>
#include <frc/geometry/Translation2d.h> #include <frc/geometry/Translation2d.h>
#include <frc/shuffleboard/Shuffleboard.h>
#include <frc/trajectory/Trajectory.h> #include <frc/trajectory/Trajectory.h>
#include <frc/trajectory/TrajectoryGenerator.h> #include <frc/trajectory/TrajectoryGenerator.h>
#include <frc/trajectory/constraint/MecanumDriveKinematicsConstraint.h> #include <frc/trajectory/constraint/MecanumDriveKinematicsConstraint.h>

View File

@@ -1,88 +0,0 @@
// 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.
#include <frc/AnalogPotentiometer.h>
#include <frc/Encoder.h>
#include <frc/Joystick.h>
#include <frc/TimedRobot.h>
#include <frc/drive/DifferentialDrive.h>
#include <frc/motorcontrol/PWMSparkMax.h>
#include <frc/shuffleboard/Shuffleboard.h>
#include <frc/shuffleboard/ShuffleboardLayout.h>
#include <frc/shuffleboard/ShuffleboardTab.h>
#include <networktables/GenericEntry.h>
#include <networktables/NetworkTableInstance.h>
/**
* This sample program provides an example for ShuffleBoard, an alternative
* to SmartDashboard for displaying values and properties of different robot
* parts.
*
* ShuffleBoard can use pre-programmed widgets to display various values, such
* as Boolean Boxes, Sliders, Graphs, and more. In addition, they can display
* things in various Tabs.
*
* For more information on how to create personal layouts and more in
* ShuffleBoard, feel free to reference the official FIRST WPILib documentation
* online.
*/
class Robot : public frc::TimedRobot {
public:
Robot() {
wpi::SendableRegistry::AddChild(&m_robotDrive, &m_left);
wpi::SendableRegistry::AddChild(&m_robotDrive, &m_right);
// Add a widget titled 'Max Speed' with a number slider.
m_maxSpeed = frc::Shuffleboard::GetTab("Configuration")
.Add("Max Speed", 1)
.WithWidget("Number Slider")
.GetEntry();
// Create a 'DriveBase' tab and add the drivetrain object to it.
frc::ShuffleboardTab& driveBaseTab = frc::Shuffleboard::GetTab("DriveBase");
driveBaseTab.Add("TankDrive", m_robotDrive);
// Put encoders in a list layout.
frc::ShuffleboardLayout& encoders =
driveBaseTab.GetLayout("Encoders", frc::BuiltInLayouts::kList)
.WithPosition(0, 0)
.WithSize(2, 2);
encoders.Add("Left Encoder", m_leftEncoder);
encoders.Add("Right Encoder", m_rightEncoder);
// Create a 'Elevator' tab and add the potentiometer and elevator motor to
// it.
frc::ShuffleboardTab& elevatorTab = frc::Shuffleboard::GetTab("Elevator");
elevatorTab.Add("Motor", m_elevatorMotor);
elevatorTab.Add("Potentiometer", m_ElevatorPot);
}
void AutonomousInit() override {
// Update the Max Output for the drivetrain.
m_robotDrive.SetMaxOutput(m_maxSpeed->GetDouble(1.0));
}
private:
frc::PWMSparkMax m_left{0};
frc::PWMSparkMax m_right{1};
frc::PWMSparkMax m_elevatorMotor{2};
frc::DifferentialDrive m_robotDrive{
[&](double output) { m_left.Set(output); },
[&](double output) { m_right.Set(output); }};
frc::Joystick m_stick{0};
frc::Encoder m_leftEncoder{0, 1};
frc::Encoder m_rightEncoder{2, 3};
frc::AnalogPotentiometer m_ElevatorPot{0};
nt::GenericEntry* m_maxSpeed;
};
#ifndef RUNNING_FRC_TESTS
int main() {
return frc::StartRobot<Robot>();
}
#endif

View File

@@ -4,41 +4,35 @@
#include "Robot.h" #include "Robot.h"
#include <frc/shuffleboard/Shuffleboard.h> #include <frc/smartdashboard/SmartDashboard.h>
#include <units/pressure.h> #include <units/pressure.h>
Robot::Robot() { Robot::Robot() {
// Publish elements to shuffleboard. // Publish elements to shuffleboard.
frc::ShuffleboardTab& tab = frc::Shuffleboard::GetTab("Pneumatics"); frc::SmartDashboard::PutData("Single Solenoid", &m_solenoid);
tab.Add("Single Solenoid", m_solenoid); frc::SmartDashboard::PutData("Double Solenoid", &m_doubleSolenoid);
tab.Add("Double Solenoid", m_doubleSolenoid); frc::SmartDashboard::PutData("Compressor", &m_compressor);
tab.Add("Compressor", m_compressor);
// Also publish some raw data
tab.AddDouble("PH Pressure [PSI]", [&] {
// Get the pressure (in PSI) from the analog sensor connected to the PH.
// This function is supported only on the PH!
// On a PCM, this function will return 0.
units::pounds_per_square_inch_t pressure = m_compressor.GetPressure();
return pressure.value();
});
tab.AddDouble("Compressor Current", [&] {
// Get compressor current draw.
units::ampere_t compressorCurrent = m_compressor.GetCurrent();
return compressorCurrent.value();
});
tab.AddBoolean("Compressor Active", [&] {
// Get whether the compressor is active.
return m_compressor.IsEnabled();
});
tab.AddBoolean("Pressure Switch", [&] {
// Get the digital pressure switch connected to the PCM/PH.
// The switch is open when the pressure is over ~120 PSI.
return m_compressor.GetPressureSwitchValue();
});
} }
void Robot::TeleopPeriodic() { void Robot::TeleopPeriodic() {
// Publish some raw data
// Get the pressure (in PSI) from the analog sensor connected to the PH.
// This function is supported only on the PH!
// On a PCM, this function will return 0.
frc::SmartDashboard::PutNumber("PH Pressure [PSI]",
m_compressor.GetPressure().value());
// Get compressor current draw.
frc::SmartDashboard::PutNumber("Compressor Current",
m_compressor.GetCurrent().value());
// Get whether the compressor is active.
frc::SmartDashboard::PutBoolean("Compressor Active",
m_compressor.IsEnabled());
// Get the digital pressure switch connected to the PCM/PH.
// The switch is open when the pressure is over ~120 PSI.
frc::SmartDashboard::PutBoolean("Pressure Switch",
m_compressor.GetPressureSwitchValue());
/* /*
* The output of GetRawButton is true/false depending on whether * The output of GetRawButton is true/false depending on whether
* the button is pressed; Set takes a boolean for whether * the button is pressed; Set takes a boolean for whether

View File

@@ -8,7 +8,6 @@
#include <frc/controller/PIDController.h> #include <frc/controller/PIDController.h>
#include <frc/geometry/Translation2d.h> #include <frc/geometry/Translation2d.h>
#include <frc/shuffleboard/Shuffleboard.h>
#include <frc/trajectory/Trajectory.h> #include <frc/trajectory/Trajectory.h>
#include <frc/trajectory/TrajectoryGenerator.h> #include <frc/trajectory/TrajectoryGenerator.h>
#include <frc2/command/Commands.h> #include <frc2/command/Commands.h>

View File

@@ -4,14 +4,13 @@
#include "Robot.h" #include "Robot.h"
#include <frc/shuffleboard/Shuffleboard.h>
#include <frc/smartdashboard/SmartDashboard.h> #include <frc/smartdashboard/SmartDashboard.h>
#include <units/length.h> #include <units/length.h>
Robot::Robot() { Robot::Robot() {
// Add the ultrasonic on the "Sensors" tab of the dashboard // Add the ultrasonic on the "Sensors" tab of the dashboard
// Data will update automatically // Data will update automatically
frc::Shuffleboard::GetTab("Sensors").Add(m_rangeFinder); frc::SmartDashboard::PutData("Sensors", &m_rangeFinder);
} }
void Robot::TeleopPeriodic() { void Robot::TeleopPeriodic() {

View File

@@ -46,7 +46,6 @@
"tags": [ "tags": [
"Hardware", "Hardware",
"Joystick", "Joystick",
"Shuffleboard",
"Pneumatics" "Pneumatics"
], ],
"foldername": "Solenoid", "foldername": "Solenoid",
@@ -119,8 +118,7 @@
"tags": [ "tags": [
"Hardware", "Hardware",
"Ultrasonic", "Ultrasonic",
"SmartDashboard", "SmartDashboard"
"Shuffleboard"
], ],
"foldername": "Ultrasonic", "foldername": "Ultrasonic",
"gradlebase": "cpp", "gradlebase": "cpp",
@@ -318,21 +316,6 @@
"gradlebase": "c", "gradlebase": "c",
"commandversion": 2 "commandversion": 2
}, },
{
"name": "Shuffleboard",
"description": "Present various data via the Shuffleboard API.",
"tags": [
"Basic Robot",
"Differential Drive",
"Elevator",
"Analog",
"Encoder",
"Shuffleboard"
],
"foldername": "ShuffleBoard",
"gradlebase": "cpp",
"commandversion": 2
},
{ {
"name": "'Traditional' Hatchbot", "name": "'Traditional' Hatchbot",
"description": "A fully-functional command-based hatchbot for the 2019 game, written in the 'traditional' style, i.e. commands are given their own classes.", "description": "A fully-functional command-based hatchbot for the 2019 game, written in the 'traditional' style, i.e. commands are given their own classes.",
@@ -341,7 +324,6 @@
"Command-based", "Command-based",
"Differential Drive", "Differential Drive",
"Encoder", "Encoder",
"Shuffleboard",
"Sendable", "Sendable",
"DataLog", "DataLog",
"Pneumatics", "Pneumatics",
@@ -359,7 +341,6 @@
"Command-based", "Command-based",
"Differential Drive", "Differential Drive",
"Encoder", "Encoder",
"Shuffleboard",
"Sendable", "Sendable",
"DataLog", "DataLog",
"Pneumatics", "Pneumatics",

View File

@@ -7,7 +7,6 @@
#include <frc/DriverStation.h> #include <frc/DriverStation.h>
#include <frc/internal/DriverStationModeThread.h> #include <frc/internal/DriverStationModeThread.h>
#include <frc/livewindow/LiveWindow.h> #include <frc/livewindow/LiveWindow.h>
#include <frc/shuffleboard/Shuffleboard.h>
#include <hal/DriverStation.h> #include <hal/DriverStation.h>
#include <networktables/NetworkTable.h> #include <networktables/NetworkTable.h>
@@ -47,7 +46,6 @@ void Robot::StartCompetition() {
} }
} else if (IsTest()) { } else if (IsTest()) {
frc::LiveWindow::SetEnabled(true); frc::LiveWindow::SetEnabled(true);
frc::Shuffleboard::EnableActuatorWidgets();
modeThread.InTest(true); modeThread.InTest(true);
Test(); Test();
modeThread.InTest(false); modeThread.InTest(false);
@@ -55,7 +53,6 @@ void Robot::StartCompetition() {
wpi::WaitForObject(event.GetHandle()); wpi::WaitForObject(event.GetHandle());
} }
frc::LiveWindow::SetEnabled(false); frc::LiveWindow::SetEnabled(false);
frc::Shuffleboard::DisableActuatorWidgets();
} else { } else {
modeThread.InTeleop(true); modeThread.InTeleop(true);
Teleop(); Teleop();

View File

@@ -10,7 +10,6 @@ import edu.wpi.first.hal.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL; import edu.wpi.first.hal.HAL;
import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.livewindow.LiveWindow; import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
@@ -331,7 +330,6 @@ public abstract class IterativeRobotBase extends RobotBase {
case kTest -> { case kTest -> {
if (m_lwEnabledInTest) { if (m_lwEnabledInTest) {
LiveWindow.setEnabled(false); LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
} }
testExit(); testExit();
} }
@@ -357,7 +355,6 @@ public abstract class IterativeRobotBase extends RobotBase {
case kTest -> { case kTest -> {
if (m_lwEnabledInTest) { if (m_lwEnabledInTest) {
LiveWindow.setEnabled(true); LiveWindow.setEnabled(true);
Shuffleboard.enableActuatorWidgets();
} }
testInit(); testInit();
m_watchdog.addEpoch("testInit()"); m_watchdog.addEpoch("testInit()");
@@ -404,8 +401,6 @@ public abstract class IterativeRobotBase extends RobotBase {
m_watchdog.addEpoch("SmartDashboard.updateValues()"); m_watchdog.addEpoch("SmartDashboard.updateValues()");
LiveWindow.updateValues(); LiveWindow.updateValues();
m_watchdog.addEpoch("LiveWindow.updateValues()"); m_watchdog.addEpoch("LiveWindow.updateValues()");
Shuffleboard.update();
m_watchdog.addEpoch("Shuffleboard.update()");
if (isSimulation()) { if (isSimulation()) {
HAL.simPeriodicBefore(); HAL.simPeriodicBefore();

View File

@@ -18,7 +18,6 @@ import edu.wpi.first.networktables.NetworkTableEvent;
import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.util.WPIUtilJNI; import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.wpilibj.livewindow.LiveWindow; import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.util.WPILibVersion; import edu.wpi.first.wpilibj.util.WPILibVersion;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -227,7 +226,6 @@ public abstract class RobotBase implements AutoCloseable {
}); });
LiveWindow.setEnabled(false); LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
} }
/** /**

View File

@@ -1,61 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* The types of layouts bundled with Shuffleboard.
*
* <pre>{@code
* ShuffleboardLayout myList = Shuffleboard.getTab("My Tab")
* .getLayout(BuiltinLayouts.kList, "My List");
* }</pre>
*/
public enum BuiltInLayouts implements LayoutType {
/**
* 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("List Layout"),
/**
* 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("Grid Layout"),
;
private final String m_layoutName;
BuiltInLayouts(String layoutName) {
m_layoutName = layoutName;
}
@Override
public String getLayoutName() {
return m_layoutName;
}
}

View File

@@ -1,476 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* The types of the widgets bundled with Shuffleboard.
*
* <p>For example, setting a number to be displayed with a slider:
*
* <pre>{@code
* GenericEntry example = Shuffleboard.getTab("My Tab")
* .add("My Number", 0)
* .withWidget(BuiltInWidgets.kNumberSlider)
* .withProperties(Map.of("min", 0, "max", 1))
* .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.
*/
public enum BuiltInWidgets implements WidgetType {
/**
* Displays a value with a simple text field. <br>
* Supported types:
*
* <ul>
* <li>String
* <li>Number
* <li>Boolean
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kTextView("Text View"),
/**
* Displays a number with a controllable slider. <br>
* Supported types:
*
* <ul>
* <li>Number
* </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("Number Slider"),
/**
* Displays a number with a view-only bar. <br>
* Supported types:
*
* <ul>
* <li>Number
* </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("Number Bar"),
/**
* Displays a number with a view-only dial. Displayed values are rounded to the nearest integer.
* <br>
* Supported types:
*
* <ul>
* <li>Number
* </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("Simple Dial"),
/**
* 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>Number array
* </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("Graph"),
/**
* Displays a boolean value as a large colored box. <br>
* Supported types:
*
* <ul>
* <li>Boolean
* </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("Boolean Box"),
/**
* Displays a boolean with a large interactive toggle button. <br>
* Supported types:
*
* <ul>
* <li>Boolean
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kToggleButton("Toggle Button"),
/**
* Displays a boolean with a fixed-size toggle switch. <br>
* Supported types:
*
* <ul>
* <li>Boolean
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kToggleSwitch("Toggle Switch"),
/**
* Displays an analog input or a raw number with a number bar. <br>
* Supported types:
*
* <ul>
* <li>Number
* <li>{@link edu.wpi.first.wpilibj.AnalogInput}
* </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("Voltage View"),
/**
* Displays a {@link edu.wpi.first.wpilibj.PowerDistribution PowerDistribution}. <br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.wpilibj.PowerDistribution}
* </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>
*/
kPowerDistribution("PDP"),
/**
* 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}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kComboBoxChooser("ComboBox Chooser"),
/**
* 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}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kSplitButtonChooser("Split Button Chooser"),
/**
* Displays an {@link edu.wpi.first.wpilibj.Encoder} displaying its speed, total traveled
* distance, and its distance per tick. <br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.wpilibj.Encoder}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kEncoder("Encoder"),
/**
* Displays a {@link edu.wpi.first.wpilibj.motorcontrol.MotorController MotorController}. The
* motor 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.motorcontrol.PWMMotorController}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.DMC60}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.Jaguar}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.PWMTalonFX}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.PWMTalonSRX}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.PWMVenom}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.PWMVictorSPX}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.SD540}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.Spark}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.Talon}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.Victor}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.VictorSP}
* <li>{@link edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup}
* <li>Any custom subclass of {@code MotorController}
* </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>
*/
kMotorController("Motor Controller"),
/**
* 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.wpilibj2.command.Command}
* <li>Any custom subclass of {@code Command}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kCommand("Command"),
/**
* 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.wpilibj2.command.PIDCommand}
* <li>Any custom subclass of {@code PIDCommand}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kPIDCommand("PID Command"),
/**
* 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.math.controller.PIDController}
* </ul>
*
* <br>
* This widget has no custom properties.
*/
kPIDController("PID Controller"),
/**
* Displays an accelerometer with a number bar displaying the magnitude of the acceleration and
* text displaying the exact value. <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("Accelerometer"),
/**
* Displays a 3-axis accelerometer with a number bar for each axis' acceleration. <br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.wpilibj.ADXL345_I2C}
* </ul>
*
* <br>
* Custom properties:
*
* <table>
* <tr><th>Name</th><th>Type</th><th>Default Value</th><th>Notes</th></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("3-Axis Accelerometer"),
/**
* Displays a gyro with a dial from 0 to 360 degrees. <br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.wpilibj.AnalogGyro}
* <li>Any custom subclass of {@code GyroBase} (such as a MXP gyro)
* </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("Gyro"),
/**
* Displays a relay with toggle buttons for each supported mode (off, on, forward, reverse). <br>
* This widget has no custom properties.
*/
kRelay("Relay"),
/**
* 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}
* </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("Differential Drivebase"),
/**
* 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}
* </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("Mecanum Drivebase"),
/**
* Displays a camera stream. <br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.cscore.VideoSource} (as long as it is streaming on an MJPEG server)
* </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("Camera Stream"),
/**
* Displays a Field2d object.<br>
* Supported types:
*
* <ul>
* <li>{@link edu.wpi.first.wpilibj.smartdashboard.Field2d}
* </ul>
*/
kField("Field"),
;
private final String m_widgetName;
BuiltInWidgets(String widgetName) {
this.m_widgetName = widgetName;
}
@Override
public String getWidgetName() {
return m_widgetName;
}
}

View File

@@ -1,57 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
/**
* A Shuffleboard widget that handles a {@link Sendable} object such as a motor controller or
* sensor.
*/
public final class ComplexWidget extends ShuffleboardWidget<ComplexWidget> {
private final Sendable m_sendable;
private SendableBuilderImpl m_builder;
ComplexWidget(ShuffleboardContainer parent, String title, Sendable sendable) {
super(parent, title);
m_sendable = sendable;
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_builder == null) {
m_builder = new SendableBuilderImpl();
m_builder.setTable(parentTable.getSubTable(getTitle()));
m_sendable.initSendable(m_builder);
m_builder.startListeners();
}
m_builder.update();
}
/**
* Enables user control of this widget in the Shuffleboard application. This method is
* package-private to prevent users from enabling control themselves. Has no effect if the
* sendable is not marked as an actuator with {@link SendableBuilder#setActuator}.
*/
void enableIfActuator() {
if (m_builder.isActuator()) {
m_builder.startLiveWindowMode();
}
}
/**
* Disables user control of this widget in the Shuffleboard application. This method is
* package-private to prevent users from enabling control themselves. Has no effect if the
* sendable is not marked as an actuator with {@link SendableBuilder#setActuator}.
*/
void disableIfActuator() {
if (m_builder.isActuator()) {
m_builder.stopLiveWindowMode();
}
}
}

View File

@@ -1,190 +0,0 @@
// 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.wpilibj.shuffleboard;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.GenericPublisher;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableType;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** A helper class for Shuffleboard containers to handle common child operations. */
final class ContainerHelper {
private final ShuffleboardContainer m_container;
private final Set<String> m_usedTitles = new HashSet<>();
private final List<ShuffleboardComponent<?>> m_components = new ArrayList<>();
private final Map<String, ShuffleboardLayout> m_layouts = new LinkedHashMap<>();
ContainerHelper(ShuffleboardContainer container) {
m_container = container;
}
List<ShuffleboardComponent<?>> getComponents() {
return m_components;
}
ShuffleboardLayout getLayout(String title, String type) {
if (!m_layouts.containsKey(title)) {
ShuffleboardLayout layout = new ShuffleboardLayout(m_container, title, type);
m_components.add(layout);
m_layouts.put(title, layout);
}
return m_layouts.get(title);
}
ShuffleboardLayout getLayout(String title) {
ShuffleboardLayout layout = m_layouts.get(title);
if (layout == null) {
throw new NoSuchElementException("No layout has been defined with the title '" + title + "'");
}
return layout;
}
ComplexWidget add(String title, Sendable sendable) {
requireNonNullParam(sendable, "sendable", "add");
checkTitle(title);
ComplexWidget widget = new ComplexWidget(m_container, title, sendable);
m_components.add(widget);
return widget;
}
ComplexWidget add(Sendable sendable) {
requireNonNullParam(sendable, "sendable", "add");
String name = SendableRegistry.getName(sendable);
if (name.isEmpty()) {
throw new IllegalArgumentException("Sendable must have a name");
}
return add(name, sendable);
}
SimpleWidget add(String title, Object defaultValue) {
requireNonNullParam(defaultValue, "defaultValue", "add");
return add(title, NetworkTableType.getStringFromObject(defaultValue), defaultValue);
}
SimpleWidget add(String title, String typeString, Object defaultValue) {
requireNonNullParam(title, "title", "add");
requireNonNullParam(defaultValue, "defaultValue", "add");
checkTitle(title);
checkNtType(defaultValue);
SimpleWidget widget = new SimpleWidget(m_container, title);
m_components.add(widget);
widget.getEntry(typeString).setDefaultValue(defaultValue);
return widget;
}
SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
precheck(title, valueSupplier, "addString");
return addSupplied(title, "string", valueSupplier, GenericPublisher::setString);
}
SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier) {
requireNonNullParam(title, "title", "addNumber");
requireNonNullParam(valueSupplier, "valueSupplier", "addNumber");
return addDouble(title, valueSupplier);
}
SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
precheck(title, valueSupplier, "addDouble");
return addSupplied(title, "double", valueSupplier::getAsDouble, GenericPublisher::setDouble);
}
SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
precheck(title, valueSupplier, "addFloat");
return addSupplied(title, "float", valueSupplier::getAsFloat, GenericPublisher::setFloat);
}
SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
precheck(title, valueSupplier, "addInteger");
return addSupplied(title, "int", valueSupplier::getAsLong, GenericPublisher::setInteger);
}
SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
precheck(title, valueSupplier, "addBoolean");
return addSupplied(title, "boolean", valueSupplier::getAsBoolean, GenericPublisher::setBoolean);
}
SuppliedValueWidget<String[]> addStringArray(String title, Supplier<String[]> valueSupplier) {
precheck(title, valueSupplier, "addStringArray");
return addSupplied(title, "string[]", valueSupplier, GenericPublisher::setStringArray);
}
SuppliedValueWidget<double[]> addDoubleArray(String title, Supplier<double[]> valueSupplier) {
precheck(title, valueSupplier, "addDoubleArray");
return addSupplied(title, "double[]", valueSupplier, GenericPublisher::setDoubleArray);
}
SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
precheck(title, valueSupplier, "addFloatArray");
return addSupplied(title, "float[]", valueSupplier, GenericPublisher::setFloatArray);
}
SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
precheck(title, valueSupplier, "addIntegerArray");
return addSupplied(title, "int[]", valueSupplier, GenericPublisher::setIntegerArray);
}
SuppliedValueWidget<boolean[]> addBooleanArray(String title, Supplier<boolean[]> valueSupplier) {
precheck(title, valueSupplier, "addBooleanArray");
return addSupplied(title, "boolean[]", valueSupplier, GenericPublisher::setBooleanArray);
}
SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return addRaw(title, "raw", valueSupplier);
}
SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
precheck(title, valueSupplier, "addRaw");
return addSupplied(title, typeString, valueSupplier, GenericPublisher::setRaw);
}
private void precheck(String title, Object valueSupplier, String methodName) {
requireNonNullParam(title, "title", methodName);
requireNonNullParam(valueSupplier, "valueSupplier", methodName);
checkTitle(title);
}
private <T> SuppliedValueWidget<T> addSupplied(
String title,
String typeString,
Supplier<T> supplier,
BiConsumer<GenericPublisher, T> setter) {
SuppliedValueWidget<T> widget =
new SuppliedValueWidget<>(m_container, title, typeString, supplier, setter);
m_components.add(widget);
return widget;
}
private static void checkNtType(Object data) {
if (!NetworkTableEntry.isValidDataType(data)) {
throw new IllegalArgumentException(
"Cannot add data of type " + data.getClass().getName() + " to Shuffleboard");
}
}
private void checkTitle(String title) {
if (m_usedTitles.contains(title)) {
throw new IllegalArgumentException("Title is already in use: " + title);
}
m_usedTitles.add(title);
}
}

View File

@@ -1,49 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* The importance of an event marker in Shuffleboard. The exact meaning of each importance level is
* up for interpretation on a team-to-team basis, but users should follow the general guidelines of
* the various importance levels. The examples given are for reference and may be ignored or
* considered to be more or less important from team to team.
*/
public enum EventImportance {
// Maintainer note: this enum is mirrored in WPILibC and in Shuffleboard
// Modifying the enum or enum strings requires a corresponding change to the C++ enum
// and the enum in Shuffleboard
/** A trivial event such as a change in command state. */
kTrivial("TRIVIAL"),
/** A low importance event such as acquisition of a game piece. */
kLow("LOW"),
/**
* A "normal" importance event, such as a transition from autonomous mode to teleoperated control.
*/
kNormal("NORMAL"),
/** A high-importance event such as scoring a game piece. */
kHigh("HIGH"),
/** A critically important event such as a brownout, component failure, or software deadlock. */
kCritical("CRITICAL");
private final String m_simpleName;
EventImportance(String simpleName) {
m_simpleName = simpleName;
}
/**
* Returns name of the given enum.
*
* @return Name of the given enum.
*/
public String getSimpleName() {
return m_simpleName;
}
}

View File

@@ -1,20 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* 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 BuiltInWidgets the built-in widget types
*/
public interface LayoutType {
/**
* Gets the string type of the layout as defined by that layout in Shuffleboard.
*
* @return The string type of the layout.
*/
String getLayoutName();
}

View File

@@ -1,65 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.wpilibj.DriverStation;
/** Controls Shuffleboard recordings via NetworkTables. */
final class RecordingController {
private static final String kRecordingTableName = "/Shuffleboard/.recording/";
private static final String kRecordingControlKey = kRecordingTableName + "RecordData";
private static final String kRecordingFileNameFormatKey = kRecordingTableName + "FileNameFormat";
private static final String kEventMarkerTableName = kRecordingTableName + "events";
private final BooleanPublisher m_recordingControlEntry;
private final StringPublisher m_recordingFileNameFormatEntry;
private final NetworkTable m_eventsTable;
RecordingController(NetworkTableInstance ntInstance) {
m_recordingControlEntry = ntInstance.getBooleanTopic(kRecordingControlKey).publish();
m_recordingFileNameFormatEntry =
ntInstance.getStringTopic(kRecordingFileNameFormatKey).publish();
m_eventsTable = ntInstance.getTable(kEventMarkerTableName);
}
public void startRecording() {
m_recordingControlEntry.set(true);
}
public void stopRecording() {
m_recordingControlEntry.set(false);
}
public void setRecordingFileNameFormat(String format) {
m_recordingFileNameFormatEntry.set(format);
}
public void clearRecordingFileNameFormat() {
m_recordingFileNameFormatEntry.set("");
}
public void addEventMarker(String name, String description, EventImportance importance) {
if (name == null || name.isEmpty()) {
DriverStation.reportError("Shuffleboard event name was not specified", true);
return;
}
if (importance == null) {
DriverStation.reportError("Shuffleboard event importance was null", true);
return;
}
String eventDescription = description == null ? "" : description;
m_eventsTable
.getSubTable(name)
.getEntry("Info")
.setStringArray(new String[] {eventDescription, importance.getSimpleName()});
}
}

View File

@@ -1,138 +0,0 @@
// 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.wpilibj.shuffleboard;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.cscore.VideoSource;
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.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<String, SendableCameraWrapper> m_wrappers = new WeakHashMap<>();
private static NetworkTable m_table;
static {
setNetworkTableInstance(NetworkTableInstance.getDefault());
}
private final String m_uri;
private StringArrayPublisher m_streams;
/**
* 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) {
this(source.getName());
}
private SendableCameraWrapper(String cameraName) {
SendableRegistry.add(this, cameraName);
m_uri = kProtocol + cameraName;
}
private SendableCameraWrapper(String cameraName, String[] cameraUrls) {
this(cameraName);
StringArrayTopic streams = new StringArrayTopic(m_table.getTopic(cameraName + "/streams"));
if (streams.exists()) {
throw new IllegalStateException(
"A camera is already being streamed with the name '" + cameraName + "'");
}
m_streams = streams.publish();
m_streams.set(cameraUrls);
}
/** Clears all cached wrapper objects. This should only be used in tests. */
static void clearWrappers() {
m_wrappers.clear();
}
@Override
public void close() {
SendableRegistry.remove(this);
if (m_streams != null) {
m_streams.close();
}
}
/**
* Sets NetworkTable instance used for camera publisher entries.
*
* @param inst NetworkTable instance
*/
public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
m_table = inst.getTable("CameraPublisher");
}
/**
* 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.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)}
*/
public static SendableCameraWrapper wrap(String cameraName, String... cameraUrls) {
if (m_wrappers.containsKey(cameraName)) {
return m_wrappers.get(cameraName);
}
requireNonNullParam(cameraName, "cameraName", "wrap");
requireNonNullParam(cameraUrls, "cameraUrls", "wrap");
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");
}
SendableCameraWrapper wrapper = new SendableCameraWrapper(cameraName, cameraUrls);
m_wrappers.put(cameraName, wrapper);
return wrapper;
}
@Override
public void initSendable(SendableBuilder builder) {
builder.addStringProperty(".ShuffleboardURI", () -> m_uri, null);
}
}

View File

@@ -1,195 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTableInstance;
/**
* The Shuffleboard class provides a mechanism with which data can be added and laid out in the
* Shuffleboard dashboard application from a robot program. Tabs and layouts can be specified, as
* well as choosing which widgets to display with and setting properties of these widgets; for
* example, programmers can specify a specific {@code boolean} value to be displayed with a toggle
* button instead of the default colored box, or set custom colors for that box.
*
* <p>For example, displaying a boolean entry with a toggle button:
*
* <pre>{@code
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
*
* <p>Changing the colors of the boolean box:
*
* <pre>{@code
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Boolean Box")
* .withProperties(Map.of("colorWhenTrue", "green", "colorWhenFalse", "maroon"))
* .getEntry();
* }</pre>
*
* <p>Specifying a parent layout. Note that the layout type must <i>always</i> be specified, even if
* the layout has already been generated by a previously defined entry.
*
* <pre>{@code
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .getLayout("List", "Example List")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
* }</pre>
*
* <p>Teams are encouraged to set up shuffleboard layouts at the start of the robot program.
*/
public final class Shuffleboard {
/** The name of the base NetworkTable into which all Shuffleboard data will be added. */
public static final String kBaseTableName = "/Shuffleboard";
private static final ShuffleboardRoot root =
new ShuffleboardInstance(NetworkTableInstance.getDefault());
private static final RecordingController recordingController =
new RecordingController(NetworkTableInstance.getDefault());
private Shuffleboard() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Updates all the values in Shuffleboard. Iterative and timed robots are pre-configured to call
* this method in the main robot loop; teams using custom robot base classes, or subclass
* SampleRobot, should make sure to call this repeatedly to keep data on the dashboard up to date.
*/
public static void update() {
root.update();
}
/**
* Gets the Shuffleboard tab with the given title, creating it if it does not already exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
public static ShuffleboardTab getTab(String title) {
return root.getTab(title);
}
/**
* Selects the tab in the dashboard with the given index in the range [0..n-1], where <i>n</i> is
* the number of tabs in the dashboard at the time this method is called.
*
* @param index the index of the tab to select
*/
public static void selectTab(int index) {
root.selectTab(index);
}
/**
* Selects the tab in the dashboard with the given title.
*
* @param title the title of the tab to select
*/
public static void selectTab(String title) {
root.selectTab(title);
}
/**
* Enables user control of widgets containing actuators: motor controllers, relays, etc. This
* should only be used when the robot is in test mode. IterativeRobotBase and SampleRobot are both
* configured to call this method when entering test mode; most users should not need to use this
* method directly.
*/
public static void enableActuatorWidgets() {
root.enableActuatorWidgets();
}
/**
* Disables user control of widgets containing actuators. For safety reasons, actuators should
* only be controlled while in test mode. IterativeRobotBase and SampleRobot are both configured
* to call this method when exiting in test mode; most users should not need to use this method
* directly.
*/
public static void disableActuatorWidgets() {
update(); // Need to update to make sure the sendable builders are initialized
root.disableActuatorWidgets();
}
/**
* Starts data recording on the dashboard. Has no effect if recording is already in progress.
*
* @see #stopRecording()
*/
public static void startRecording() {
recordingController.startRecording();
}
/**
* Stops data recording on the dashboard. Has no effect if no recording is in progress.
*
* @see #startRecording()
*/
public static void stopRecording() {
recordingController.stopRecording();
}
/**
* Sets the file name format for new recording files to use. If recording is in progress when this
* method is called, it will continue to use the same file. New recordings will use the format.
*
* <p>To avoid recording files overwriting each other, make sure to use unique recording file
* names. File name formats accept templates for inserting the date and time when the recording
* started with the {@code ${date}} and {@code ${time}} templates, respectively. For example, the
* default format is {@code "recording-${time}"} and recording files created with it will have
* names like {@code "recording-2018.01.15.sbr"}. Users are <strong>strongly</strong> recommended
* to use the {@code ${time}} template to ensure unique file names.
*
* @param format the format for the
* @see #clearRecordingFileNameFormat()
*/
public static void setRecordingFileNameFormat(String format) {
recordingController.setRecordingFileNameFormat(format);
}
/**
* Clears the custom name format for recording files. New recordings will use the default format.
*
* @see #setRecordingFileNameFormat(String)
*/
public static void clearRecordingFileNameFormat() {
recordingController.clearRecordingFileNameFormat();
}
/**
* Notifies Shuffleboard of an event. Events can range from as trivial as a change in a command
* state to as critical as a total power loss or component failure. If Shuffleboard is recording,
* the event will also be recorded.
*
* <p>If {@code name} is {@code null} or empty, or {@code importance} is {@code null}, then no
* event will be sent and an error will be printed to the driver station.
*
* @param name the name of the event
* @param description a description of the event
* @param importance the importance of the event
*/
public static void addEventMarker(String name, String description, EventImportance importance) {
recordingController.addEventMarker(name, description, importance);
}
/**
* Notifies Shuffleboard of an event. Events can range from as trivial as a change in a command
* state to as critical as a total power loss or component failure. If Shuffleboard is recording,
* the event will also be recorded.
*
* <p>If {@code name} is {@code null} or empty, or {@code importance} is {@code null}, then no
* event will be sent and an error will be printed to the driver station.
*
* @param name the name of the event
* @param importance the importance of the event
*/
public static void addEventMarker(String name, EventImportance importance) {
addEventMarker(name, "", importance);
}
}

View File

@@ -1,177 +0,0 @@
// 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.wpilibj.shuffleboard;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.NetworkTable;
import java.util.Map;
/**
* A generic component in Shuffleboard.
*
* @param <C> the self type
*/
public abstract class ShuffleboardComponent<C extends ShuffleboardComponent<C>>
implements ShuffleboardValue {
private final ShuffleboardContainer m_parent;
private final String m_title;
private String m_type;
private Map<String, Object> m_properties;
private boolean m_metadataDirty = true;
private int m_column = -1;
private int m_row = -1;
private int m_width = -1;
private int m_height = -1;
/**
* Constructs a ShuffleboardComponent.
*
* @param parent The parent container.
* @param title The component title.
* @param type The component type.
*/
protected ShuffleboardComponent(ShuffleboardContainer parent, String title, String type) {
m_parent = requireNonNullParam(parent, "parent", "ShuffleboardComponent");
m_title = requireNonNullParam(title, "title", "ShuffleboardComponent");
m_type = type;
}
/**
* Constructs a ShuffleboardComponent.
*
* @param parent The parent container.
* @param title The component title.
*/
protected ShuffleboardComponent(ShuffleboardContainer parent, String title) {
this(parent, title, null);
}
/**
* Returns the parent container.
*
* @return The parent container.
*/
public final ShuffleboardContainer getParent() {
return m_parent;
}
/**
* Sets the component type.
*
* @param type The component type.
*/
protected final void setType(String type) {
m_type = type;
m_metadataDirty = true;
}
/**
* Returns the component type.
*
* @return The component type.
*/
public final String getType() {
return m_type;
}
@Override
public final String getTitle() {
return m_title;
}
/** Gets the custom properties for this component. May be null. */
final Map<String, Object> getProperties() {
return m_properties;
}
/**
* Sets custom properties for this component. Property names are case- and whitespace-insensitive
* (capitalization and spaces do not matter).
*
* @param properties the properties for this component
* @return this component
*/
@SuppressWarnings("unchecked")
public final C withProperties(Map<String, Object> properties) {
m_properties = properties;
m_metadataDirty = true;
return (C) this;
}
/**
* Sets the position of this component in the tab. This has no effect if this component is inside
* a layout.
*
* <p>If the position of a single component is set, it is recommended to set the positions of
* <i>all</i> components inside a tab to prevent Shuffleboard from automatically placing another
* component there before the one with the specific position is sent.
*
* @param columnIndex the column in the tab to place this component
* @param rowIndex the row in the tab to place this component
* @return this component
*/
@SuppressWarnings("unchecked")
public final C withPosition(int columnIndex, int rowIndex) {
m_column = columnIndex;
m_row = rowIndex;
m_metadataDirty = true;
return (C) this;
}
/**
* Sets the size of this component in the tab. This has no effect if this component is inside a
* layout.
*
* @param width how many columns wide the component should be
* @param height how many rows high the component should be
* @return this component
*/
@SuppressWarnings("unchecked")
public final C withSize(int width, int height) {
m_width = width;
m_height = height;
m_metadataDirty = true;
return (C) this;
}
/**
* Builds NT metadata.
*
* @param metaTable The NT metadata table.
*/
protected final void buildMetadata(NetworkTable metaTable) {
if (!m_metadataDirty) {
return;
}
// Component type
if (getType() == null) {
metaTable.getEntry("PreferredComponent").unpublish();
} else {
metaTable.getEntry("PreferredComponent").setString(getType());
}
// Tile size
if (m_width <= 0 || m_height <= 0) {
metaTable.getEntry("Size").unpublish();
} else {
metaTable.getEntry("Size").setDoubleArray(new double[] {m_width, m_height});
}
// Tile position
if (m_column < 0 || m_row < 0) {
metaTable.getEntry("Position").unpublish();
} else {
metaTable.getEntry("Position").setDoubleArray(new double[] {m_column, m_row});
}
// Custom properties
if (getProperties() != null) {
NetworkTable propTable = metaTable.getSubTable("Properties");
getProperties().forEach((name, value) -> propTable.getEntry(name).setValue(value));
}
m_metadataDirty = false;
}
}

View File

@@ -1,363 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.cscore.VideoSource;
import edu.wpi.first.networktables.NetworkTableType;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** Common interface for objects that can contain shuffleboard components. */
public sealed interface ShuffleboardContainer extends ShuffleboardValue
permits ShuffleboardLayout, ShuffleboardTab {
/**
* Gets the components that are direct children of this container.
*
* @return The components that are direct children of this container.
*/
List<ShuffleboardComponent<?>> getComponents();
/**
* 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(String title, String 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
*/
default ShuffleboardLayout getLayout(String title, LayoutType layoutType) {
return getLayout(title, layoutType.getLayoutName());
}
/**
* 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 NoSuchElementException if no layout has yet been defined with the given title
*/
ShuffleboardLayout getLayout(String title);
/**
* Adds a widget to this container to display the given sendable.
*
* @param title the title of the widget
* @param sendable the sendable 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(String 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
*/
default ComplexWidget add(String title, VideoSource video) {
return add(title, SendableCameraWrapper.wrap(video));
}
/**
* Adds a widget to this container to display the given sendable.
*
* @param sendable the sendable to display
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title, or if the sendable's name has not been specified
*/
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.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #addPersistent(String, Object) add(String title, Object defaultValue)
*/
SimpleWidget add(String title, Object defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param typeString the NT type string
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #addPersistent(String, Object) add(String title, Object defaultValue)
*/
SimpleWidget add(String title, String typeString, 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<String[]> addStringArray(String title, Supplier<String[]> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<double[]> addDoubleArray(String title, Supplier<double[]> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<boolean[]> addBooleanArray(String title, Supplier<boolean[]> valueSupplier);
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
default SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return addRaw(title, "raw", valueSupplier);
}
/**
* 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
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param typeString the NT type string for the value
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier);
/**
* Adds a widget to this container to display a simple piece of data. Unlike {@link #add(String,
* Object)}, the value in the widget will be saved on the robot and will be used when the robot
* program next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #add(String, Object) add(String title, Object defaultValue)
*/
default SimpleWidget addPersistent(String title, Object defaultValue) {
return addPersistent(title, NetworkTableType.getStringFromObject(defaultValue), defaultValue);
}
/**
* Adds a widget to this container to display a simple piece of data. Unlike {@link #add(String,
* Object)}, the value in the widget will be saved on the robot and will be used when the robot
* program next starts rather than {@code defaultValue}.
*
* @param title the title of the widget
* @param typeString the NT type string
* @param defaultValue the default value of the widget
* @return a widget to display the sendable data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
* @see #add(String, Object) add(String title, Object defaultValue)
*/
default SimpleWidget addPersistent(String title, String typeString, Object defaultValue) {
SimpleWidget widget = add(title, defaultValue);
widget.getEntry(typeString).getTopic().setPersistent(true);
return widget;
}
}

View File

@@ -1,114 +0,0 @@
// 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.wpilibj.shuffleboard;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.hal.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.networktables.StringPublisher;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
final class ShuffleboardInstance implements ShuffleboardRoot {
private final Map<String, ShuffleboardTab> m_tabs = new LinkedHashMap<>();
private boolean m_reported = false; // NOPMD redundant field initializer
private boolean m_tabsChanged = false; // NOPMD redundant field initializer
private final NetworkTable m_rootTable;
private final NetworkTable m_rootMetaTable;
private final StringPublisher m_selectedTabPub;
/**
* Creates a new Shuffleboard instance.
*
* @param ntInstance the NetworkTables instance to use
*/
ShuffleboardInstance(NetworkTableInstance ntInstance) {
requireNonNullParam(ntInstance, "ntInstance", "ShuffleboardInstance");
m_rootTable = ntInstance.getTable(Shuffleboard.kBaseTableName);
m_rootMetaTable = m_rootTable.getSubTable(".metadata");
m_selectedTabPub =
m_rootMetaTable.getStringTopic("Selected").publish(PubSubOption.keepDuplicates(true));
}
@Override
public ShuffleboardTab getTab(String title) {
requireNonNullParam(title, "title", "getTab");
if (!m_reported) {
HAL.report(tResourceType.kResourceType_Shuffleboard, 0);
m_reported = true;
}
if (!m_tabs.containsKey(title)) {
m_tabs.put(title, new ShuffleboardTab(this, title));
m_tabsChanged = true;
}
return m_tabs.get(title);
}
@Override
public void update() {
if (m_tabsChanged) {
String[] tabTitles =
m_tabs.values().stream().map(ShuffleboardTab::getTitle).toArray(String[]::new);
m_rootMetaTable.getEntry("Tabs").setStringArray(tabTitles);
m_tabsChanged = false;
}
for (ShuffleboardTab tab : m_tabs.values()) {
String title = tab.getTitle();
tab.buildInto(m_rootTable, m_rootMetaTable.getSubTable(title));
}
}
@Override
public void enableActuatorWidgets() {
applyToAllComplexWidgets(ComplexWidget::enableIfActuator);
}
@Override
public void disableActuatorWidgets() {
applyToAllComplexWidgets(ComplexWidget::disableIfActuator);
}
@Override
public void selectTab(int index) {
selectTab(Integer.toString(index));
}
@Override
public void selectTab(String title) {
m_selectedTabPub.set(title);
}
/**
* Applies the function {@code func} to all complex widgets in this root, regardless of how they
* are nested.
*
* @param func the function to apply to all complex widgets
*/
private void applyToAllComplexWidgets(Consumer<ComplexWidget> func) {
for (ShuffleboardTab tab : m_tabs.values()) {
apply(tab, func);
}
}
/**
* Applies the function {@code func} to all complex widgets in {@code container}. Helper method
* for {@link #applyToAllComplexWidgets}.
*/
private void apply(ShuffleboardContainer container, Consumer<ComplexWidget> func) {
for (ShuffleboardComponent<?> component : container.getComponents()) {
if (component instanceof ComplexWidget widget) {
func.accept(widget);
}
if (component instanceof ShuffleboardContainer nestedContainer) {
apply(nestedContainer, func);
}
}
}
}

View File

@@ -1,141 +0,0 @@
// 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.wpilibj.shuffleboard;
import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** A layout in a Shuffleboard tab. Layouts can contain widgets and other layouts. */
public final class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout>
implements ShuffleboardContainer {
private static final String kSmartDashboardType = "ShuffleboardLayout";
private final ContainerHelper m_helper = new ContainerHelper(this);
ShuffleboardLayout(ShuffleboardContainer parent, String title, String type) {
super(parent, title, requireNonNullParam(type, "type", "ShuffleboardLayout"));
}
@Override
public List<ShuffleboardComponent<?>> getComponents() {
return m_helper.getComponents();
}
@Override
public ShuffleboardLayout getLayout(String title, String type) {
return m_helper.getLayout(title, type);
}
@Override
public ShuffleboardLayout getLayout(String title) {
return m_helper.getLayout(title);
}
@Override
public ComplexWidget add(String title, Sendable sendable) {
return m_helper.add(title, sendable);
}
@Override
public ComplexWidget add(Sendable sendable) {
return m_helper.add(sendable);
}
@Override
public SimpleWidget add(String title, Object defaultValue) {
return m_helper.add(title, defaultValue);
}
@Override
public SimpleWidget add(String title, String typeString, Object defaultValue) {
return m_helper.add(title, typeString, defaultValue);
}
@Override
public SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
return m_helper.addString(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier) {
return m_helper.addNumber(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
return m_helper.addDouble(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
return m_helper.addFloat(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
return m_helper.addInteger(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
return m_helper.addBoolean(title, valueSupplier);
}
@Override
public SuppliedValueWidget<String[]> addStringArray(
String title, Supplier<String[]> valueSupplier) {
return m_helper.addStringArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<double[]> addDoubleArray(
String title, Supplier<double[]> valueSupplier) {
return m_helper.addDoubleArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
return m_helper.addFloatArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
return m_helper.addIntegerArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<boolean[]> addBooleanArray(
String title, Supplier<boolean[]> valueSupplier) {
return m_helper.addBooleanArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, typeString, valueSupplier);
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
NetworkTable table = parentTable.getSubTable(getTitle());
table.getEntry(".type").setString(kSmartDashboardType);
table
.getEntry(".type")
.getTopic()
.setProperty("SmartDashboard", "\"" + kSmartDashboardType + "\"");
for (ShuffleboardComponent<?> component : getComponents()) {
component.buildInto(table, metaTable.getSubTable(component.getTitle()));
}
}
}

View File

@@ -1,45 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* The root of the data placed in Shuffleboard. It contains the tabs, but no data is placed directly
* in the root.
*
* <p>This class is package-private to minimize API surface area.
*/
interface ShuffleboardRoot {
/**
* Gets the tab with the given title, creating it if it does not already exist.
*
* @param title the title of the tab
* @return the tab with the given title
*/
ShuffleboardTab getTab(String title);
/** Updates all tabs. */
void update();
/** Enables all widgets in Shuffleboard that offer user control over actuators. */
void enableActuatorWidgets();
/** Disables all widgets in Shuffleboard that offer user control over actuators. */
void disableActuatorWidgets();
/**
* Selects the tab in the dashboard with the given index in the range [0..n-1], where <i>n</i> is
* the number of tabs in the dashboard at the time this method is called.
*
* @param index the index of the tab to select
*/
void selectTab(int index);
/**
* Selects the tab in the dashboard with the given title.
*
* @param title the title of the tab to select
*/
void selectTab(String title);
}

View File

@@ -1,154 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
* Represents a tab in the Shuffleboard dashboard. Widgets can be added to the tab with {@link
* #add(Sendable)}, {@link #add(String, Object)}, and {@link #add(String, Sendable)}. Widgets can
* also be added to layouts with {@link #getLayout(String, String)}; layouts can be nested
* arbitrarily deep (note that too many levels may make deeper components unusable).
*/
public final class ShuffleboardTab implements ShuffleboardContainer {
private static final String kSmartDashboardType = "ShuffleboardTab";
private final ContainerHelper m_helper = new ContainerHelper(this);
private final ShuffleboardRoot m_root;
private final String m_title;
ShuffleboardTab(ShuffleboardRoot root, String title) {
m_root = root;
m_title = title;
}
@Override
public String getTitle() {
return m_title;
}
ShuffleboardRoot getRoot() {
return m_root;
}
@Override
public List<ShuffleboardComponent<?>> getComponents() {
return m_helper.getComponents();
}
@Override
public ShuffleboardLayout getLayout(String title, String type) {
return m_helper.getLayout(title, type);
}
@Override
public ShuffleboardLayout getLayout(String title) {
return m_helper.getLayout(title);
}
@Override
public ComplexWidget add(String title, Sendable sendable) {
return m_helper.add(title, sendable);
}
@Override
public ComplexWidget add(Sendable sendable) {
return m_helper.add(sendable);
}
@Override
public SimpleWidget add(String title, Object defaultValue) {
return m_helper.add(title, defaultValue);
}
@Override
public SimpleWidget add(String title, String typeString, Object defaultValue) {
return m_helper.add(title, typeString, defaultValue);
}
@Override
public SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
return m_helper.addString(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier) {
return m_helper.addNumber(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
return m_helper.addDouble(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
return m_helper.addFloat(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
return m_helper.addInteger(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
return m_helper.addBoolean(title, valueSupplier);
}
@Override
public SuppliedValueWidget<String[]> addStringArray(
String title, Supplier<String[]> valueSupplier) {
return m_helper.addStringArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<double[]> addDoubleArray(
String title, Supplier<double[]> valueSupplier) {
return m_helper.addDoubleArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
return m_helper.addFloatArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
return m_helper.addIntegerArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<boolean[]> addBooleanArray(
String title, Supplier<boolean[]> valueSupplier) {
return m_helper.addBooleanArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, typeString, valueSupplier);
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
NetworkTable tabTable = parentTable.getSubTable(m_title);
tabTable.getEntry(".type").setString(kSmartDashboardType);
tabTable
.getEntry(".type")
.getTopic()
.setProperty("SmartDashboard", "\"" + kSmartDashboardType + "\"");
for (ShuffleboardComponent<?> component : m_helper.getComponents()) {
component.buildInto(tabTable, metaTable.getSubTable(component.getTitle()));
}
}
}

View File

@@ -1,27 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
interface ShuffleboardValue {
/**
* Gets the title of this Shuffleboard value.
*
* @return The title of this Shuffleboard value.
*/
String getTitle();
/**
* Builds the entries for this value.
*
* @param parentTable the table containing all the data for the parent. Values that require a
* complex entry or table structure should call {@code parentTable.getSubTable(getTitle())} to
* get the table to put data into. Values that only use a single entry should call {@code
* parentTable.getEntry(getTitle())} to get that entry.
* @param metaTable the table containing all the metadata for this value and its sub-values
*/
void buildInto(NetworkTable parentTable, NetworkTable metaTable);
}

View File

@@ -1,46 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* Abstract superclass for widgets.
*
* <p>This class is package-private to minimize API surface area.
*
* @param <W> the self type
*/
abstract class ShuffleboardWidget<W extends ShuffleboardWidget<W>>
extends ShuffleboardComponent<W> {
ShuffleboardWidget(ShuffleboardContainer parent, String title) {
super(parent, title);
}
/**
* 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
* @see BuiltInWidgets
*/
public final W withWidget(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 third-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
*/
@SuppressWarnings("unchecked")
public final W withWidget(String widgetType) {
setType(widgetType);
return (W) this;
}
}

View File

@@ -1,68 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.GenericEntry;
import edu.wpi.first.networktables.NetworkTable;
/** A Shuffleboard widget that handles a single data point such as a number or string. */
public final class SimpleWidget extends ShuffleboardWidget<SimpleWidget> implements AutoCloseable {
private String m_typeString = "";
private GenericEntry m_entry;
SimpleWidget(ShuffleboardContainer parent, String title) {
super(parent, title);
}
/**
* Gets the NetworkTable entry that contains the data for this widget.
*
* @return The NetworkTable entry that contains the data for this widget.
*/
public GenericEntry getEntry() {
if (m_entry == null) {
forceGenerate();
}
return m_entry;
}
/**
* Gets the NetworkTable entry that contains the data for this widget.
*
* @param typeString NetworkTable type string
* @return The NetworkTable entry that contains the data for this widget.
*/
public GenericEntry getEntry(String typeString) {
if (m_entry == null) {
m_typeString = typeString;
forceGenerate();
}
return m_entry;
}
@Override
public void close() {
if (m_entry != null) {
m_entry.close();
}
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_entry == null) {
m_entry = parentTable.getTopic(getTitle()).getGenericEntry(m_typeString);
}
}
private void forceGenerate() {
ShuffleboardContainer parent = getParent();
while (parent instanceof ShuffleboardLayout layout) {
parent = layout.getParent();
}
ShuffleboardTab tab = (ShuffleboardTab) parent;
tab.getRoot().update();
}
}

View File

@@ -1,71 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.BooleanTopic;
import edu.wpi.first.networktables.GenericPublisher;
import edu.wpi.first.networktables.NetworkTable;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
/**
* A Shuffleboard widget whose value is provided by user code.
*
* @param <T> the type of values in the widget
*/
public final class SuppliedValueWidget<T> extends ShuffleboardWidget<SuppliedValueWidget<T>>
implements AutoCloseable {
private final String m_typeString;
private final Supplier<T> m_supplier;
private final BiConsumer<GenericPublisher, T> m_setter;
private BooleanPublisher m_controllablePub;
private GenericPublisher m_entry;
/**
* Package-private constructor for use by the Shuffleboard API.
*
* @param parent the parent container for the widget
* @param title the title of the widget
* @param typeString the NetworkTables string type
* @param supplier the supplier for values to place in the NetworkTable entry
* @param setter the function for placing values in the NetworkTable entry
*/
SuppliedValueWidget(
ShuffleboardContainer parent,
String title,
String typeString,
Supplier<T> supplier,
BiConsumer<GenericPublisher, T> setter) {
super(parent, title);
m_typeString = typeString;
m_supplier = supplier;
m_setter = setter;
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_controllablePub == null) {
m_controllablePub = new BooleanTopic(metaTable.getTopic("Controllable")).publish();
m_controllablePub.set(false);
}
if (m_entry == null) {
m_entry = parentTable.getTopic(getTitle()).genericPublish(m_typeString);
}
m_setter.accept(m_entry, m_supplier.get());
}
@Override
public void close() {
if (m_controllablePub != null) {
m_controllablePub.close();
}
if (m_entry != null) {
m_entry.close();
}
}
}

View File

@@ -1,20 +0,0 @@
// 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.wpilibj.shuffleboard;
/**
* 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
*/
public interface WidgetType {
/**
* Gets the string type of the widget as defined by that widget in Shuffleboard.
*
* @return The string type of the widget.
*/
String getWidgetName();
}

View File

@@ -1,22 +0,0 @@
// 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.wpilibj.shuffleboard;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
/** A mock sendable that marks itself as an actuator. */
public class MockActuatorSendable implements Sendable {
@SuppressWarnings("this-escape")
public MockActuatorSendable(String name) {
SendableRegistry.add(this, name);
}
@Override
public void initSendable(SendableBuilder builder) {
builder.setActuator(true);
}
}

View File

@@ -1,65 +0,0 @@
// 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.wpilibj.shuffleboard;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import edu.wpi.first.networktables.NetworkTableInstance;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class SendableCameraWrapperTest {
NetworkTableInstance m_inst;
@BeforeEach
void setup() {
m_inst = NetworkTableInstance.create();
SendableCameraWrapper.clearWrappers();
}
@AfterEach
void tearDown() {
m_inst.close();
}
@Test
void testNullCameraName() {
assertThrows(NullPointerException.class, () -> SendableCameraWrapper.wrap(null, ""));
}
@Test
void testEmptyCameraName() {
assertThrows(IllegalArgumentException.class, () -> SendableCameraWrapper.wrap("", ""));
}
@Test
void testNullUrlArray() {
assertThrows(
NullPointerException.class, () -> SendableCameraWrapper.wrap("name", (String[]) null));
}
@Test
void testNullUrlInArray() {
assertThrows(NullPointerException.class, () -> SendableCameraWrapper.wrap("name", "url", null));
}
@Test
void testEmptyUrlArray() {
assertThrows(IllegalArgumentException.class, () -> SendableCameraWrapper.wrap("name"));
}
@Test
void testUrlsAddedToNt() {
SendableCameraWrapper.wrap("name", "url1", "url2");
assertArrayEquals(
new String[] {"url1", "url2"},
NetworkTableInstance.getDefault()
.getEntry("/CameraPublisher/name/streams")
.getValue()
.getStringArray());
}
}

View File

@@ -1,158 +0,0 @@
// 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.wpilibj.shuffleboard;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.networktables.GenericEntry;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableEvent.Kind;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.networktables.StringSubscriber;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class ShuffleboardInstanceTest {
private NetworkTableInstance m_ntInstance;
private ShuffleboardInstance m_shuffleboardInstance;
@BeforeEach
void setupInstance() {
m_ntInstance = NetworkTableInstance.create();
m_shuffleboardInstance = new ShuffleboardInstance(m_ntInstance);
}
@AfterEach
void tearDownInstance() {
m_ntInstance.close();
}
@Test
void testPathFluent() {
GenericEntry entry =
m_shuffleboardInstance
.getTab("Tab Title")
.getLayout("Layout Title", "List Layout")
.add("Data", "string")
.withWidget("Text View")
.getEntry();
assertAll(
() -> assertEquals("string", entry.getString(null), "Wrong entry value"),
() ->
assertEquals(
"/Shuffleboard/Tab Title/Layout Title/Data",
entry.getTopic().getName(),
"Entry path generated incorrectly"));
}
@Test
void testNestedLayoutsFluent() {
GenericEntry entry =
m_shuffleboardInstance
.getTab("Tab")
.getLayout("First", "List")
.getLayout("Second", "List")
.getLayout("Third", "List")
.getLayout("Fourth", "List")
.add("Value", "string")
.getEntry();
assertAll(
() -> assertEquals("string", entry.getString(null), "Wrong entry value"),
() ->
assertEquals(
"/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
entry.getTopic().getName(),
"Entry path generated incorrectly"));
}
@Test
void testNestedLayoutsOop() {
ShuffleboardTab tab = m_shuffleboardInstance.getTab("Tab");
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");
GenericEntry entry = widget.getEntry();
assertAll(
() -> assertEquals("string", entry.getString(null), "Wrong entry value"),
() ->
assertEquals(
"/Shuffleboard/Tab/First/Second/Third/Fourth/Value",
entry.getTopic().getName(),
"Entry path generated incorrectly"));
}
@Test
void testLayoutTypeIsSet() {
String layoutType = "Type";
m_shuffleboardInstance.getTab("Tab").getLayout("Title", layoutType);
m_shuffleboardInstance.update();
NetworkTableEntry entry =
m_ntInstance.getEntry("/Shuffleboard/.metadata/Tab/Title/PreferredComponent");
assertEquals(layoutType, entry.getString("Not Set"), "Layout type not set");
}
@Test
void testNestedActuatorWidgetsAreDisabled() {
m_shuffleboardInstance
.getTab("Tab")
.getLayout("Title", "Layout")
.add(new MockActuatorSendable("Actuator"));
NetworkTableEntry controllableEntry =
m_ntInstance.getEntry("/Shuffleboard/Tab/Title/Actuator/.controllable");
m_shuffleboardInstance.update();
// Note: we use the unsafe `getBoolean()` method because if the value is NOT a boolean, or if it
// is not present, then something has clearly gone very, very wrong
boolean controllable = controllableEntry.getValue().getBoolean();
// Sanity check
assertTrue(controllable, "The nested actuator widget should be enabled by default");
m_shuffleboardInstance.disableActuatorWidgets();
controllable = controllableEntry.getValue().getBoolean();
assertFalse(controllable, "The nested actuator widget should have been disabled");
}
@Disabled("Fails often at counter assertion 'expected: <2> but was: <1>'")
@Test
void testDuplicateSelectTabs() {
int listener = 0;
AtomicInteger counter = new AtomicInteger();
try (StringSubscriber subscriber =
m_ntInstance
.getStringTopic("/Shuffleboard/.metadata/Selected")
.subscribe("", PubSubOption.keepDuplicates(true))) {
listener =
m_ntInstance.addListener(
subscriber,
EnumSet.of(Kind.kValueAll, Kind.kImmediate),
event -> counter.incrementAndGet());
// There shouldn't be anything there
assertEquals(0, counter.get());
m_shuffleboardInstance.selectTab("tab1");
m_shuffleboardInstance.selectTab("tab1");
assertTrue(m_ntInstance.waitForListenerQueue(1.0), "Listener queue timed out!");
assertEquals(2, counter.get());
} finally {
m_ntInstance.removeListener(listener);
}
}
}

View File

@@ -1,25 +0,0 @@
// 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.wpilibj.shuffleboard;
import static org.junit.jupiter.api.Assertions.assertSame;
import edu.wpi.first.wpilibj.UtilityClassTest;
import org.junit.jupiter.api.Test;
class ShuffleboardTest extends UtilityClassTest<Shuffleboard> {
ShuffleboardTest() {
super(Shuffleboard.class);
}
// Most relevant tests are in ShuffleboardTabTest
@Test
void testTabObjectsCached() {
ShuffleboardTab tab1 = Shuffleboard.getTab("testTabObjectsCached");
ShuffleboardTab tab2 = Shuffleboard.getTab("testTabObjectsCached");
assertSame(tab1, tab2, "Tab objects were not cached");
}
}

View File

@@ -1,112 +0,0 @@
// 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.wpilibj.shuffleboard;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class SuppliedValueWidgetTest {
private NetworkTableInstance m_ntInstance;
private ShuffleboardInstance m_instance;
@BeforeEach
void setup() {
m_ntInstance = NetworkTableInstance.create();
m_instance = new ShuffleboardInstance(m_ntInstance);
}
@AfterEach
void tearDown() {
m_ntInstance.close();
}
@Test
void testAddString() {
AtomicInteger count = new AtomicInteger(0);
m_instance.getTab("Tab").addString("Title", () -> Integer.toString(count.incrementAndGet()));
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertEquals("1", entry.getString(null));
m_instance.update();
assertEquals("2", entry.getString(null));
}
@Test
void testAddDouble() {
AtomicInteger num = new AtomicInteger(0);
m_instance.getTab("Tab").addNumber("Title", num::incrementAndGet);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertEquals(1, entry.getDouble(0));
m_instance.update();
assertEquals(2, entry.getDouble(0));
}
@Test
void testAddBoolean() {
boolean[] bool = {false};
m_instance.getTab("Tab").addBoolean("Title", () -> bool[0] = !bool[0]);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertTrue(entry.getBoolean(false));
m_instance.update();
assertFalse(entry.getBoolean(true));
}
@Test
void testAddStringArray() {
String[] arr = {"foo", "bar"};
m_instance.getTab("Tab").addStringArray("Title", () -> arr);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertArrayEquals(arr, entry.getStringArray(new String[0]));
}
@Test
void testAddDoubleArray() {
double[] arr = {0, 1};
m_instance.getTab("Tab").addDoubleArray("Title", () -> arr);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertArrayEquals(arr, entry.getDoubleArray(new double[0]));
}
@Test
void testAddBooleanArray() {
boolean[] arr = {true, false};
m_instance.getTab("Tab").addBooleanArray("Title", () -> arr);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertArrayEquals(arr, entry.getBooleanArray(new boolean[0]));
}
@Test
void testAddRawBytes() {
byte[] arr = {0, 1, 2, 3};
m_instance.getTab("Tab").addRaw("Title", () -> arr);
NetworkTableEntry entry = m_ntInstance.getEntry("/Shuffleboard/Tab/Title");
m_instance.update();
assertArrayEquals(arr, entry.getRaw(new byte[0]));
}
}

View File

@@ -68,7 +68,6 @@
"tags": [ "tags": [
"Hardware", "Hardware",
"Joystick", "Joystick",
"Shuffleboard",
"Pneumatics" "Pneumatics"
], ],
"foldername": "solenoid", "foldername": "solenoid",
@@ -108,8 +107,7 @@
"tags": [ "tags": [
"Hardware", "Hardware",
"Ultrasonic", "Ultrasonic",
"SmartDashboard", "SmartDashboard"
"Shuffleboard"
], ],
"foldername": "ultrasonic", "foldername": "ultrasonic",
"gradlebase": "java", "gradlebase": "java",
@@ -308,22 +306,6 @@
"mainclass": "Main", "mainclass": "Main",
"commandversion": 2 "commandversion": 2
}, },
{
"name": "Shuffleboard",
"description": "Present various data via the Shuffleboard API.",
"tags": [
"Basic Robot",
"Differential Drive",
"Elevator",
"Analog",
"Encoder",
"Shuffleboard"
],
"foldername": "shuffleboard",
"gradlebase": "java",
"mainclass": "Main",
"commandversion": 2
},
{ {
"name": "'Traditional' Hatchbot", "name": "'Traditional' Hatchbot",
"description": "A fully-functional command-based hatchbot for the 2019 game, written in the 'traditional' style, i.e. commands are given their own classes.", "description": "A fully-functional command-based hatchbot for the 2019 game, written in the 'traditional' style, i.e. commands are given their own classes.",
@@ -333,7 +315,6 @@
"Differential Drive", "Differential Drive",
"Encoder", "Encoder",
"Pneumatics", "Pneumatics",
"Shuffleboard",
"Sendable", "Sendable",
"DataLog", "DataLog",
"XboxController" "XboxController"
@@ -352,7 +333,6 @@
"Differential Drive", "Differential Drive",
"Encoder", "Encoder",
"Pneumatics", "Pneumatics",
"Shuffleboard",
"Sendable", "Sendable",
"DataLog", "DataLog",
"PS4Controller" "PS4Controller"

View File

@@ -9,11 +9,9 @@ import edu.wpi.first.wpilibj.examples.hatchbotinlined.Constants.OIConstants;
import edu.wpi.first.wpilibj.examples.hatchbotinlined.commands.Autos; import edu.wpi.first.wpilibj.examples.hatchbotinlined.commands.Autos;
import edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems.DriveSubsystem; import edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems.DriveSubsystem;
import edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems.HatchSubsystem; import edu.wpi.first.wpilibj.examples.hatchbotinlined.subsystems.HatchSubsystem;
import edu.wpi.first.wpilibj.shuffleboard.EventImportance;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.Commands; import edu.wpi.first.wpilibj2.command.Commands;
import edu.wpi.first.wpilibj2.command.button.CommandPS4Controller; import edu.wpi.first.wpilibj2.command.button.CommandPS4Controller;
@@ -64,28 +62,11 @@ public class RobotContainer {
m_chooser.addOption("Complex Auto", m_complexAuto); m_chooser.addOption("Complex Auto", m_complexAuto);
// Put the chooser on the dashboard // Put the chooser on the dashboard
Shuffleboard.getTab("Autonomous").add(m_chooser); SmartDashboard.putData("Autonomous", m_chooser);
// Put subsystems to dashboard. // Put subsystems to dashboard.
Shuffleboard.getTab("Drivetrain").add(m_robotDrive); SmartDashboard.putData("Drivetrain", m_robotDrive);
Shuffleboard.getTab("HatchSubsystem").add(m_hatchSubsystem); SmartDashboard.putData("HatchSubsystem", m_hatchSubsystem);
// Set the scheduler to log Shuffleboard events for command initialize, interrupt, finish
CommandScheduler.getInstance()
.onCommandInitialize(
command ->
Shuffleboard.addEventMarker(
"Command initialized", command.getName(), EventImportance.kNormal));
CommandScheduler.getInstance()
.onCommandInterrupt(
command ->
Shuffleboard.addEventMarker(
"Command interrupted", command.getName(), EventImportance.kNormal));
CommandScheduler.getInstance()
.onCommandFinish(
command ->
Shuffleboard.addEventMarker(
"Command finished", command.getName(), EventImportance.kNormal));
} }
/** /**

View File

@@ -17,11 +17,9 @@ import edu.wpi.first.wpilibj.examples.hatchbottraditional.commands.HalveDriveSpe
import edu.wpi.first.wpilibj.examples.hatchbottraditional.commands.ReleaseHatch; import edu.wpi.first.wpilibj.examples.hatchbottraditional.commands.ReleaseHatch;
import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem; import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.DriveSubsystem;
import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem; import edu.wpi.first.wpilibj.examples.hatchbottraditional.subsystems.HatchSubsystem;
import edu.wpi.first.wpilibj.shuffleboard.EventImportance;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj2.command.Command; import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.CommandScheduler;
import edu.wpi.first.wpilibj2.command.button.JoystickButton; import edu.wpi.first.wpilibj2.command.button.JoystickButton;
/** /**
@@ -71,32 +69,10 @@ public class RobotContainer {
m_chooser.addOption("Complex Auto", m_complexAuto); m_chooser.addOption("Complex Auto", m_complexAuto);
// Put the chooser on the dashboard // Put the chooser on the dashboard
Shuffleboard.getTab("Autonomous").add(m_chooser); SmartDashboard.putData("Autonomous", m_chooser);
// Put subsystems to dashboard. // Put subsystems to dashboard.
Shuffleboard.getTab("Drivetrain").add(m_robotDrive); SmartDashboard.putData("Drivetrain", m_robotDrive);
Shuffleboard.getTab("HatchSubsystem").add(m_hatchSubsystem); SmartDashboard.putData("HatchSubsystem", m_hatchSubsystem);
// Log Shuffleboard events for command initialize, execute, finish, interrupt
CommandScheduler.getInstance()
.onCommandInitialize(
command ->
Shuffleboard.addEventMarker(
"Command initialized", command.getName(), EventImportance.kNormal));
CommandScheduler.getInstance()
.onCommandExecute(
command ->
Shuffleboard.addEventMarker(
"Command executed", command.getName(), EventImportance.kNormal));
CommandScheduler.getInstance()
.onCommandFinish(
command ->
Shuffleboard.addEventMarker(
"Command finished", command.getName(), EventImportance.kNormal));
CommandScheduler.getInstance()
.onCommandInterrupt(
command ->
Shuffleboard.addEventMarker(
"Command interrupted", command.getName(), EventImportance.kNormal));
} }
/** /**

View File

@@ -1,25 +0,0 @@
// 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.wpilibj.examples.shuffleboard;
import edu.wpi.first.wpilibj.RobotBase;
/**
* Do NOT add any static variables to this class, or any initialization at all. Unless you know what
* you are doing, do not modify this file except to change the parameter class to the startRobot
* call.
*/
public final class Main {
private Main() {}
/**
* Main initialization function. Do not perform any initialization here.
*
* <p>If you change your main robot class, change the parameter type.
*/
public static void main(String... args) {
RobotBase.startRobot(Robot::new);
}
}

View File

@@ -1,66 +0,0 @@
// 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.wpilibj.examples.shuffleboard;
import edu.wpi.first.networktables.GenericEntry;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.AnalogPotentiometer;
import edu.wpi.first.wpilibj.Encoder;
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
import edu.wpi.first.wpilibj.shuffleboard.BuiltInLayouts;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardLayout;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab;
public class Robot extends TimedRobot {
private final PWMSparkMax m_leftDriveMotor = new PWMSparkMax(0);
private final PWMSparkMax m_rightDriveMotor = new PWMSparkMax(1);
private final DifferentialDrive m_tankDrive =
new DifferentialDrive(m_leftDriveMotor::set, m_rightDriveMotor::set);
private final Encoder m_leftEncoder = new Encoder(0, 1);
private final Encoder m_rightEncoder = new Encoder(2, 3);
private final PWMSparkMax m_elevatorMotor = new PWMSparkMax(2);
private final AnalogPotentiometer m_elevatorPot = new AnalogPotentiometer(0);
private final GenericEntry m_maxSpeed;
/** Called once at the beginning of the robot program. */
public Robot() {
SendableRegistry.addChild(m_tankDrive, m_leftDriveMotor);
SendableRegistry.addChild(m_tankDrive, m_rightDriveMotor);
// Add a 'max speed' widget to a tab named 'Configuration', using a number slider
// The widget will be placed in the second column and row and will be TWO columns wide
m_maxSpeed =
Shuffleboard.getTab("Configuration")
.add("Max Speed", 1)
.withWidget("Number Slider")
.withPosition(1, 1)
.withSize(2, 1)
.getEntry();
// Add the tank drive and encoders to a 'Drivebase' tab
ShuffleboardTab driveBaseTab = Shuffleboard.getTab("Drivebase");
driveBaseTab.add("Tank Drive", m_tankDrive);
// Put both encoders in a list layout
ShuffleboardLayout encoders =
driveBaseTab.getLayout("Encoders", BuiltInLayouts.kList).withPosition(0, 0).withSize(2, 2);
encoders.add("Left Encoder", m_leftEncoder);
encoders.add("Right Encoder", m_rightEncoder);
// Add the elevator motor and potentiometer to an 'Elevator' tab
ShuffleboardTab elevatorTab = Shuffleboard.getTab("Elevator");
elevatorTab.add("Motor", m_elevatorMotor);
elevatorTab.add("Potentiometer", m_elevatorPot);
}
@Override
public void autonomousInit() {
// Read the value of the 'max speed' widget from the dashboard
m_tankDrive.setMaxOutput(m_maxSpeed.getDouble(1.0));
}
}

View File

@@ -10,8 +10,7 @@ import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.PneumaticsModuleType; import edu.wpi.first.wpilibj.PneumaticsModuleType;
import edu.wpi.first.wpilibj.Solenoid; import edu.wpi.first.wpilibj.Solenoid;
import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab;
/** /**
* This is a sample program showing the use of the solenoid classes during operator control. Three * This is a sample program showing the use of the solenoid classes during operator control. Three
@@ -46,29 +45,28 @@ public class Robot extends TimedRobot {
/** Called once at the beginning of the robot program. */ /** Called once at the beginning of the robot program. */
public Robot() { public Robot() {
// Publish elements to shuffleboard. // Publish elements to dashboard.
ShuffleboardTab tab = Shuffleboard.getTab("Pneumatics"); SmartDashboard.putData("Single Solenoid", m_solenoid);
tab.add("Single Solenoid", m_solenoid); SmartDashboard.putData("Double Solenoid", m_doubleSolenoid);
tab.add("Double Solenoid", m_doubleSolenoid); SmartDashboard.putData("Compressor", m_compressor);
tab.add("Compressor", m_compressor);
// Also publish some raw data
// Get the pressure (in PSI) from the analog sensor connected to the PH.
// This function is supported only on the PH!
// On a PCM, this function will return 0.
tab.addDouble("PH Pressure [PSI]", m_compressor::getPressure);
// Get compressor current draw.
tab.addDouble("Compressor Current", m_compressor::getCurrent);
// Get whether the compressor is active.
tab.addBoolean("Compressor Active", m_compressor::isEnabled);
// Get the digital pressure switch connected to the PCM/PH.
// The switch is open when the pressure is over ~120 PSI.
tab.addBoolean("Pressure Switch", m_compressor::getPressureSwitchValue);
} }
@SuppressWarnings("PMD.UnconditionalIfStatement") @SuppressWarnings("PMD.UnconditionalIfStatement")
@Override @Override
public void teleopPeriodic() { public void teleopPeriodic() {
// Publish some raw data
// Get the pressure (in PSI) from the analog sensor connected to the PH.
// This function is supported only on the PH!
// On a PCM, this function will return 0.
SmartDashboard.putNumber("PH Pressure [PSI]", m_compressor.getPressure());
// Get compressor current draw.
SmartDashboard.putNumber("Compressor Current", m_compressor.getCurrent());
// Get whether the compressor is active.
SmartDashboard.putBoolean("Compressor Active", m_compressor.isEnabled());
// Get the digital pressure switch connected to the PCM/PH.
// The switch is open when the pressure is over ~120 PSI.
SmartDashboard.putBoolean("Pressure Switch", m_compressor.getPressureSwitchValue());
/* /*
* The output of GetRawButton is true/false depending on whether * The output of GetRawButton is true/false depending on whether
* the button is pressed; Set takes a boolean for whether * the button is pressed; Set takes a boolean for whether

View File

@@ -6,7 +6,6 @@ package edu.wpi.first.wpilibj.examples.ultrasonic;
import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.Ultrasonic; import edu.wpi.first.wpilibj.Ultrasonic;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
/** /**
@@ -21,7 +20,7 @@ public class Robot extends TimedRobot {
public Robot() { public Robot() {
// Add the ultrasonic on the "Sensors" tab of the dashboard // Add the ultrasonic on the "Sensors" tab of the dashboard
// Data will update automatically // Data will update automatically
Shuffleboard.getTab("Sensors").add(m_rangeFinder); SmartDashboard.putData("Sensors", m_rangeFinder);
} }
@Override @Override