[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

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

View File

@@ -18,7 +18,6 @@ import edu.wpi.first.networktables.NetworkTableEvent;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.util.WPILibVersion;
import java.io.File;
import java.io.IOException;
@@ -227,7 +226,6 @@ public abstract class RobotBase implements AutoCloseable {
});
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();
}