mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Add fluent builders for more flexibly adding data to Shuffleboard (#1022)
This commit is contained in:
committed by
Peter Johnson
parent
ac7dfa5042
commit
175c6c1f01
@@ -9,6 +9,7 @@ package edu.wpi.first.wpilibj;
|
||||
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
|
||||
|
||||
/**
|
||||
@@ -200,6 +201,7 @@ public abstract class IterativeRobotBase extends RobotBase {
|
||||
// or from power-on.
|
||||
if (m_lastMode != Mode.kDisabled) {
|
||||
LiveWindow.setEnabled(false);
|
||||
Shuffleboard.disableActuatorWidgets();
|
||||
disabledInit();
|
||||
m_watchdog.addEpoch("disabledInit()");
|
||||
m_lastMode = Mode.kDisabled;
|
||||
@@ -213,6 +215,7 @@ public abstract class IterativeRobotBase extends RobotBase {
|
||||
// mode or from power-on.
|
||||
if (m_lastMode != Mode.kAutonomous) {
|
||||
LiveWindow.setEnabled(false);
|
||||
Shuffleboard.disableActuatorWidgets();
|
||||
autonomousInit();
|
||||
m_watchdog.addEpoch("autonomousInit()");
|
||||
m_lastMode = Mode.kAutonomous;
|
||||
@@ -226,6 +229,7 @@ public abstract class IterativeRobotBase extends RobotBase {
|
||||
// from power-on.
|
||||
if (m_lastMode != Mode.kTeleop) {
|
||||
LiveWindow.setEnabled(false);
|
||||
Shuffleboard.disableActuatorWidgets();
|
||||
teleopInit();
|
||||
m_watchdog.addEpoch("teleopInit()");
|
||||
m_lastMode = Mode.kTeleop;
|
||||
@@ -239,6 +243,7 @@ public abstract class IterativeRobotBase extends RobotBase {
|
||||
// power-on.
|
||||
if (m_lastMode != Mode.kTest) {
|
||||
LiveWindow.setEnabled(true);
|
||||
Shuffleboard.enableActuatorWidgets();
|
||||
testInit();
|
||||
m_watchdog.addEpoch("testInit()");
|
||||
m_lastMode = Mode.kTest;
|
||||
@@ -255,6 +260,7 @@ public abstract class IterativeRobotBase extends RobotBase {
|
||||
SmartDashboard.updateValues();
|
||||
|
||||
LiveWindow.updateValues();
|
||||
Shuffleboard.update();
|
||||
|
||||
// Warn on loop time overruns
|
||||
if (m_watchdog.isExpired()) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.hal.HALUtil;
|
||||
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.util.WPILibVersion;
|
||||
|
||||
/**
|
||||
@@ -91,6 +92,7 @@ public abstract class RobotBase implements AutoCloseable {
|
||||
inst.getTable("LiveWindow").getSubTable(".status").getEntry("LW Enabled").setBoolean(false);
|
||||
|
||||
LiveWindow.setEnabled(false);
|
||||
Shuffleboard.disableActuatorWidgets();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
||||
@@ -11,6 +11,7 @@ import edu.wpi.first.hal.FRCNetComm.tInstances;
|
||||
import edu.wpi.first.hal.FRCNetComm.tResourceType;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
|
||||
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
|
||||
|
||||
/**
|
||||
* A simple robot base class that knows the standard FRC competition states (disabled, autonomous,
|
||||
@@ -142,6 +143,7 @@ public class SampleRobot extends RobotBase {
|
||||
}
|
||||
} else if (isTest()) {
|
||||
LiveWindow.setEnabled(true);
|
||||
Shuffleboard.enableActuatorWidgets();
|
||||
m_ds.InTest(true);
|
||||
test();
|
||||
m_ds.InTest(false);
|
||||
@@ -149,6 +151,7 @@ public class SampleRobot extends RobotBase {
|
||||
Timer.delay(0.01);
|
||||
}
|
||||
LiveWindow.setEnabled(false);
|
||||
Shuffleboard.disableActuatorWidgets();
|
||||
} else {
|
||||
m_ds.InOperatorControl(true);
|
||||
operatorControl();
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.wpilibj.Sendable;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilder;
|
||||
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
|
||||
|
||||
/**
|
||||
* A Shuffleboard widget that handles a {@link Sendable} object such as a speed 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.updateTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.wpilibj.Sendable;
|
||||
|
||||
/**
|
||||
* 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 type, String title) {
|
||||
if (!m_layouts.containsKey(title)) {
|
||||
ShuffleboardLayout layout = new ShuffleboardLayout(m_container, type, title);
|
||||
m_components.add(layout);
|
||||
m_layouts.put(title, layout);
|
||||
}
|
||||
return m_layouts.get(title);
|
||||
}
|
||||
|
||||
ComplexWidget add(String title, Sendable sendable) {
|
||||
checkTitle(title);
|
||||
ComplexWidget widget = new ComplexWidget(m_container, title, sendable);
|
||||
m_components.add(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
|
||||
if (sendable.getName() == null || sendable.getName().isEmpty()) {
|
||||
throw new IllegalArgumentException("Sendable must have a name");
|
||||
}
|
||||
return add(sendable.getName(), sendable);
|
||||
}
|
||||
|
||||
SimpleWidget add(String title, Object defaultValue) {
|
||||
Objects.requireNonNull(title, "Title cannot be null");
|
||||
Objects.requireNonNull(defaultValue, "Default value cannot be null");
|
||||
checkTitle(title);
|
||||
checkNtType(defaultValue);
|
||||
|
||||
SimpleWidget widget = new SimpleWidget(m_container, title);
|
||||
m_components.add(widget);
|
||||
widget.getEntry().setDefaultValue(defaultValue);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import 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
|
||||
* 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>
|
||||
*
|
||||
* <p>Teams are encouraged to set up shuffleboard layouts at the start of the robot program.</p>
|
||||
*/
|
||||
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());
|
||||
|
||||
// TODO usage reporting
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables user control of widgets containing actuators: speed 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
protected ShuffleboardComponent(ShuffleboardContainer parent, String title, String type) {
|
||||
m_parent = Objects.requireNonNull(parent, "Parent cannot be null");
|
||||
m_title = Objects.requireNonNull(title, "Title cannot be null");
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
protected ShuffleboardComponent(ShuffleboardContainer parent, String title) {
|
||||
this(parent, title, null);
|
||||
}
|
||||
|
||||
public final ShuffleboardContainer getParent() {
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
protected final void setType(String type) {
|
||||
m_type = type;
|
||||
m_metadataDirty = true;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public final C withSize(int width, int height) {
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_metadataDirty = true;
|
||||
return (C) this;
|
||||
}
|
||||
|
||||
protected final void buildMetadata(NetworkTable metaTable) {
|
||||
if (!m_metadataDirty) {
|
||||
return;
|
||||
}
|
||||
// Component type
|
||||
if (getType() == null) {
|
||||
metaTable.getEntry("PreferredComponent").delete();
|
||||
} else {
|
||||
metaTable.getEntry("PreferredComponent").forceSetString(getType());
|
||||
}
|
||||
|
||||
// Tile size
|
||||
if (m_width <= 0 || m_height <= 0) {
|
||||
metaTable.getEntry("Size").delete();
|
||||
} else {
|
||||
metaTable.getEntry("Size").setDoubleArray(new double[]{m_width, m_height});
|
||||
}
|
||||
|
||||
// Tile position
|
||||
if (m_column < 0 || m_row < 0) {
|
||||
metaTable.getEntry("Position").delete();
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import edu.wpi.first.wpilibj.Sendable;
|
||||
|
||||
/**
|
||||
* Common interface for objects that can contain shuffleboard components.
|
||||
*/
|
||||
public interface ShuffleboardContainer extends ShuffleboardValue {
|
||||
|
||||
/**
|
||||
* Gets 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.
|
||||
*
|
||||
* @param type the type of the layout, eg "List" or "Grid"
|
||||
* @param title the title of the layout
|
||||
* @return the layout
|
||||
*/
|
||||
ShuffleboardLayout getLayout(String type, 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) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* 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 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) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
throws IllegalArgumentException {
|
||||
SimpleWidget widget = add(title, defaultValue);
|
||||
widget.getEntry().setPersistent();
|
||||
return widget;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
|
||||
final class ShuffleboardInstance implements ShuffleboardRoot {
|
||||
private final Map<String, ShuffleboardTab> m_tabs = new LinkedHashMap<>();
|
||||
|
||||
private boolean m_tabsChanged = false; // NOPMD redundant field initializer
|
||||
private final NetworkTable m_rootTable;
|
||||
private final NetworkTable m_rootMetaTable;
|
||||
|
||||
ShuffleboardInstance(NetworkTableInstance ntInstance) {
|
||||
Objects.requireNonNull(ntInstance, "NetworkTable instance cannot be null");
|
||||
m_rootTable = ntInstance.getTable(Shuffleboard.kBaseTableName);
|
||||
m_rootMetaTable = m_rootTable.getSubTable(".metadata");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleboardTab getTab(String title) {
|
||||
Objects.requireNonNull(title, "Tab title cannot be null");
|
||||
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").forceSetStringArray(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
func.accept((ComplexWidget) component);
|
||||
}
|
||||
if (component instanceof ShuffleboardContainer) {
|
||||
apply((ShuffleboardContainer) component, func);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.wpilibj.Sendable;
|
||||
|
||||
/**
|
||||
* A layout in a Shuffleboard tab. Layouts can contain widgets and other layouts.
|
||||
*/
|
||||
public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout>
|
||||
implements ShuffleboardContainer {
|
||||
private final ContainerHelper m_helper = new ContainerHelper(this);
|
||||
|
||||
ShuffleboardLayout(ShuffleboardContainer parent, String name, String type) {
|
||||
super(parent, Objects.requireNonNull(type, "Layout type must be specified"), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ShuffleboardComponent<?>> getComponents() {
|
||||
return m_helper.getComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShuffleboardLayout getLayout(String type, String title) {
|
||||
return m_helper.getLayout(type, title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexWidget add(String title, Sendable sendable) throws IllegalArgumentException {
|
||||
return m_helper.add(title, sendable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
|
||||
return m_helper.add(sendable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleWidget add(String title, Object defaultValue) throws IllegalArgumentException {
|
||||
return m_helper.add(title, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
|
||||
buildMetadata(metaTable);
|
||||
NetworkTable table = parentTable.getSubTable(getTitle());
|
||||
table.getEntry(".type").setString("ShuffleboardLayout");
|
||||
for (ShuffleboardComponent<?> component : getComponents()) {
|
||||
component.buildInto(table, metaTable.getSubTable(component.getTitle()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.wpilibj.Sendable;
|
||||
|
||||
/**
|
||||
* 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 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 type, String title) {
|
||||
return m_helper.getLayout(type, title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexWidget add(String title, Sendable sendable) {
|
||||
return m_helper.add(title, sendable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexWidget add(Sendable sendable) throws IllegalArgumentException {
|
||||
return m_helper.add(sendable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleWidget add(String title, Object defaultValue) {
|
||||
return m_helper.add(title, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
|
||||
NetworkTable tabTable = parentTable.getSubTable(m_title);
|
||||
tabTable.getEntry(".type").setString("ShuffleboardTab");
|
||||
for (ShuffleboardComponent<?> component : m_helper.getComponents()) {
|
||||
component.buildInto(tabTable, metaTable.getSubTable(component.getTitle()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
|
||||
interface ShuffleboardValue {
|
||||
|
||||
/**
|
||||
* Gets 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public final W withWidget(String widgetType) {
|
||||
setType(widgetType);
|
||||
return (W) this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
package edu.wpi.first.wpilibj.shuffleboard;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
|
||||
/**
|
||||
* A Shuffleboard widget that handles a single data point such as a number or string.
|
||||
*/
|
||||
public final class SimpleWidget extends ShuffleboardWidget<SimpleWidget> {
|
||||
private NetworkTableEntry m_entry;
|
||||
|
||||
SimpleWidget(ShuffleboardContainer parent, String title) {
|
||||
super(parent, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the NetworkTable entry that contains the data for this widget.
|
||||
*/
|
||||
public NetworkTableEntry getEntry() {
|
||||
if (m_entry == null) {
|
||||
forceGenerate();
|
||||
}
|
||||
return m_entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
|
||||
buildMetadata(metaTable);
|
||||
if (m_entry == null) {
|
||||
m_entry = parentTable.getEntry(getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
private void forceGenerate() {
|
||||
ShuffleboardContainer parent = getParent();
|
||||
while (parent instanceof ShuffleboardLayout) {
|
||||
parent = ((ShuffleboardLayout) parent).getParent();
|
||||
}
|
||||
ShuffleboardTab tab = (ShuffleboardTab) parent;
|
||||
tab.getRoot().update();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user