001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.shuffleboard;
006
007import edu.wpi.first.networktables.NetworkTableInstance;
008
009/**
010 * The Shuffleboard class provides a mechanism with which data can be added and laid out in the
011 * Shuffleboard dashboard application from a robot program. Tabs and layouts can be specified, as
012 * well as choosing which widgets to display with and setting properties of these widgets; for
013 * example, programmers can specify a specific {@code boolean} value to be displayed with a toggle
014 * button instead of the default colored box, or set custom colors for that box.
015 *
016 * <p>For example, displaying a boolean entry with a toggle button:
017 *
018 * <pre>{@code
019 * GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
020 *   .add("My Boolean", false)
021 *   .withWidget("Toggle Button")
022 *   .getEntry();
023 * }</pre>
024 *
025 * <p>Changing the colors of the boolean box:
026 *
027 * <pre>{@code
028 * GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
029 *   .add("My Boolean", false)
030 *   .withWidget("Boolean Box")
031 *   .withProperties(Map.of("colorWhenTrue", "green", "colorWhenFalse", "maroon"))
032 *   .getEntry();
033 * }</pre>
034 *
035 * <p>Specifying a parent layout. Note that the layout type must <i>always</i> be specified, even if
036 * the layout has already been generated by a previously defined entry.
037 *
038 * <pre>{@code
039 * GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
040 *   .getLayout("List", "Example List")
041 *   .add("My Boolean", false)
042 *   .withWidget("Toggle Button")
043 *   .getEntry();
044 * }</pre>
045 *
046 * <p>Teams are encouraged to set up shuffleboard layouts at the start of the robot program.
047 */
048public final class Shuffleboard {
049  /** The name of the base NetworkTable into which all Shuffleboard data will be added. */
050  public static final String kBaseTableName = "/Shuffleboard";
051
052  private static final ShuffleboardRoot root =
053      new ShuffleboardInstance(NetworkTableInstance.getDefault());
054  private static final RecordingController recordingController =
055      new RecordingController(NetworkTableInstance.getDefault());
056
057  private Shuffleboard() {
058    throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
059  }
060
061  /**
062   * Updates all the values in Shuffleboard. Iterative and timed robots are pre-configured to call
063   * this method in the main robot loop; teams using custom robot base classes, or subclass
064   * SampleRobot, should make sure to call this repeatedly to keep data on the dashboard up to date.
065   */
066  public static void update() {
067    root.update();
068  }
069
070  /**
071   * Gets the Shuffleboard tab with the given title, creating it if it does not already exist.
072   *
073   * @param title the title of the tab
074   * @return the tab with the given title
075   */
076  public static ShuffleboardTab getTab(String title) {
077    return root.getTab(title);
078  }
079
080  /**
081   * Selects the tab in the dashboard with the given index in the range [0..n-1], where <i>n</i> is
082   * the number of tabs in the dashboard at the time this method is called.
083   *
084   * @param index the index of the tab to select
085   */
086  public static void selectTab(int index) {
087    root.selectTab(index);
088  }
089
090  /**
091   * Selects the tab in the dashboard with the given title.
092   *
093   * @param title the title of the tab to select
094   */
095  public static void selectTab(String title) {
096    root.selectTab(title);
097  }
098
099  /**
100   * Enables user control of widgets containing actuators: motor controllers, relays, etc. This
101   * should only be used when the robot is in test mode. IterativeRobotBase and SampleRobot are both
102   * configured to call this method when entering test mode; most users should not need to use this
103   * method directly.
104   */
105  public static void enableActuatorWidgets() {
106    root.enableActuatorWidgets();
107  }
108
109  /**
110   * Disables user control of widgets containing actuators. For safety reasons, actuators should
111   * only be controlled while in test mode. IterativeRobotBase and SampleRobot are both configured
112   * to call this method when exiting in test mode; most users should not need to use this method
113   * directly.
114   */
115  public static void disableActuatorWidgets() {
116    update(); // Need to update to make sure the sendable builders are initialized
117    root.disableActuatorWidgets();
118  }
119
120  /**
121   * Starts data recording on the dashboard. Has no effect if recording is already in progress.
122   *
123   * @see #stopRecording()
124   */
125  public static void startRecording() {
126    recordingController.startRecording();
127  }
128
129  /**
130   * Stops data recording on the dashboard. Has no effect if no recording is in progress.
131   *
132   * @see #startRecording()
133   */
134  public static void stopRecording() {
135    recordingController.stopRecording();
136  }
137
138  /**
139   * Sets the file name format for new recording files to use. If recording is in progress when this
140   * method is called, it will continue to use the same file. New recordings will use the format.
141   *
142   * <p>To avoid recording files overwriting each other, make sure to use unique recording file
143   * names. File name formats accept templates for inserting the date and time when the recording
144   * started with the {@code ${date}} and {@code ${time}} templates, respectively. For example, the
145   * default format is {@code "recording-${time}"} and recording files created with it will have
146   * names like {@code "recording-2018.01.15.sbr"}. Users are <strong>strongly</strong> recommended
147   * to use the {@code ${time}} template to ensure unique file names.
148   *
149   * @param format the format for the
150   * @see #clearRecordingFileNameFormat()
151   */
152  public static void setRecordingFileNameFormat(String format) {
153    recordingController.setRecordingFileNameFormat(format);
154  }
155
156  /**
157   * Clears the custom name format for recording files. New recordings will use the default format.
158   *
159   * @see #setRecordingFileNameFormat(String)
160   */
161  public static void clearRecordingFileNameFormat() {
162    recordingController.clearRecordingFileNameFormat();
163  }
164
165  /**
166   * Notifies Shuffleboard of an event. Events can range from as trivial as a change in a command
167   * state to as critical as a total power loss or component failure. If Shuffleboard is recording,
168   * the event will also be recorded.
169   *
170   * <p>If {@code name} is {@code null} or empty, or {@code importance} is {@code null}, then no
171   * event will be sent and an error will be printed to the driver station.
172   *
173   * @param name the name of the event
174   * @param description a description of the event
175   * @param importance the importance of the event
176   */
177  public static void addEventMarker(String name, String description, EventImportance importance) {
178    recordingController.addEventMarker(name, description, importance);
179  }
180
181  /**
182   * Notifies Shuffleboard of an event. Events can range from as trivial as a change in a command
183   * state to as critical as a total power loss or component failure. If Shuffleboard is recording,
184   * the event will also be recorded.
185   *
186   * <p>If {@code name} is {@code null} or empty, or {@code importance} is {@code null}, then no
187   * event will be sent and an error will be printed to the driver station.
188   *
189   * @param name the name of the event
190   * @param importance the importance of the event
191   */
192  public static void addEventMarker(String name, EventImportance importance) {
193    addEventMarker(name, "", importance);
194  }
195}