001/*----------------------------------------------------------------------------*/
002/* Copyright (c) 2008-2018 FIRST. All Rights Reserved.                        */
003/* Open Source Software - may be modified and shared by FRC teams. The code   */
004/* must be accompanied by the FIRST BSD license file in the root directory of */
005/* the project.                                                               */
006/*----------------------------------------------------------------------------*/
007
008package edu.wpi.first.wpilibj.livewindow;
009
010import java.util.HashMap;
011import java.util.Map;
012
013import edu.wpi.first.networktables.NetworkTable;
014import edu.wpi.first.networktables.NetworkTableEntry;
015import edu.wpi.first.networktables.NetworkTableInstance;
016import edu.wpi.first.wpilibj.command.Scheduler;
017import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
018import edu.wpi.first.wpilibj.Sendable;
019
020
021/**
022 * The LiveWindow class is the public interface for putting sensors and actuators on the
023 * LiveWindow.
024 */
025public class LiveWindow {
026  private static class Component {
027    Component(Sendable sendable, Sendable parent) {
028      m_sendable = sendable;
029      m_parent = parent;
030    }
031
032    final Sendable m_sendable;
033    Sendable m_parent;
034    final SendableBuilderImpl m_builder = new SendableBuilderImpl();
035    boolean m_firstTime = true;
036    boolean m_telemetryEnabled = true;
037  }
038
039  private static final Map<Object, Component> components = new HashMap<>();
040  private static final NetworkTable liveWindowTable =
041      NetworkTableInstance.getDefault().getTable("LiveWindow");
042  private static final NetworkTable statusTable = liveWindowTable.getSubTable(".status");
043  private static final NetworkTableEntry enabledEntry = statusTable.getEntry("LW Enabled");
044  private static boolean startLiveWindow = false;
045  private static boolean liveWindowEnabled = false;
046  private static boolean telemetryEnabled = true;
047
048  public static synchronized boolean isEnabled() {
049    return liveWindowEnabled;
050  }
051
052  /**
053   * Set the enabled state of LiveWindow. If it's being enabled, turn off the scheduler and remove
054   * all the commands from the queue and enable all the components registered for LiveWindow. If
055   * it's being disabled, stop all the registered components and reenable the scheduler. TODO: add
056   * code to disable PID loops when enabling LiveWindow. The commands should reenable the PID loops
057   * themselves when they get rescheduled. This prevents arms from starting to move around, etc.
058   * after a period of adjusting them in LiveWindow mode.
059   */
060  public static synchronized void setEnabled(boolean enabled) {
061    if (liveWindowEnabled != enabled) {
062      Scheduler scheduler = Scheduler.getInstance();
063      if (enabled) {
064        System.out.println("Starting live window mode.");
065        scheduler.disable();
066        scheduler.removeAll();
067      } else {
068        System.out.println("stopping live window mode.");
069        for (Component component : components.values()) {
070          component.m_builder.stopLiveWindowMode();
071        }
072        scheduler.enable();
073      }
074      startLiveWindow = enabled;
075      liveWindowEnabled = enabled;
076      enabledEntry.setBoolean(enabled);
077    }
078  }
079
080  /**
081   * The run method is called repeatedly to keep the values refreshed on the screen in test mode.
082   * @deprecated No longer required
083   */
084  @Deprecated
085  public static void run() {
086    updateValues();
087  }
088
089  /**
090   * Add a Sensor associated with the subsystem and with call it by the given name.
091   *
092   * @param subsystem The subsystem this component is part of.
093   * @param name      The name of this component.
094   * @param component A LiveWindowSendable component that represents a sensor.
095   * @deprecated Use {@link Sendable#setName(String, String)} instead.
096   */
097  @Deprecated
098  public static synchronized void addSensor(String subsystem, String name, Sendable component) {
099    add(component);
100    component.setName(subsystem, name);
101  }
102
103  /**
104   * Add Sensor to LiveWindow. The components are shown with the type and channel like this: Gyro[1]
105   * for a gyro object connected to the first analog channel.
106   *
107   * @param moduleType A string indicating the type of the module used in the naming (above)
108   * @param channel    The channel number the device is connected to
109   * @param component  A reference to the object being added
110   * @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int)} instead.
111   */
112  @Deprecated
113  public static void addSensor(String moduleType, int channel, Sendable component) {
114    add(component);
115    component.setName("Ungrouped", moduleType + "[" + channel + "]");
116  }
117
118  /**
119   * Add an Actuator associated with the subsystem and with call it by the given name.
120   *
121   * @param subsystem The subsystem this component is part of.
122   * @param name      The name of this component.
123   * @param component A LiveWindowSendable component that represents a actuator.
124   * @deprecated Use {@link Sendable#setName(String, String)} instead.
125   */
126  @Deprecated
127  public static synchronized void addActuator(String subsystem, String name, Sendable component) {
128    add(component);
129    component.setName(subsystem, name);
130  }
131
132  /**
133   * Add Actuator to LiveWindow. The components are shown with the module type, slot and channel
134   * like this: Servo[1,2] for a servo object connected to the first digital module and PWM port 2.
135   *
136   * @param moduleType A string that defines the module name in the label for the value
137   * @param channel    The channel number the device is plugged into (usually PWM)
138   * @param component  The reference to the object being added
139   * @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int)} instead.
140   */
141  @Deprecated
142  public static void addActuator(String moduleType, int channel, Sendable component) {
143    add(component);
144    component.setName("Ungrouped", moduleType + "[" + channel + "]");
145  }
146
147  /**
148   * Add Actuator to LiveWindow. The components are shown with the module type, slot and channel
149   * like this: Servo[1,2] for a servo object connected to the first digital module and PWM port 2.
150   *
151   * @param moduleType   A string that defines the module name in the label for the value
152   * @param moduleNumber The number of the particular module type
153   * @param channel      The channel number the device is plugged into (usually PWM)
154   * @param component    The reference to the object being added
155   * @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int, int)} instead.
156   */
157  @Deprecated
158  public static void addActuator(String moduleType, int moduleNumber, int channel,
159                                 Sendable component) {
160    add(component);
161    component.setName("Ungrouped", moduleType + "[" + moduleNumber + "," + channel + "]");
162  }
163
164  /**
165   * Add a component to the LiveWindow.
166   *
167   * @param sendable component to add
168   */
169  public static synchronized void add(Sendable sendable) {
170    components.putIfAbsent(sendable, new Component(sendable, null));
171  }
172
173  /**
174   * Add a child component to a component.
175   *
176   * @param parent parent component
177   * @param child child component
178   */
179  public static synchronized void addChild(Sendable parent, Object child) {
180    Component component = components.get(child);
181    if (component == null) {
182      component = new Component(null, parent);
183      components.put(child, component);
184    } else {
185      component.m_parent = parent;
186    }
187    component.m_telemetryEnabled = false;
188  }
189
190  /**
191   * Remove a component from the LiveWindow.
192   *
193   * @param sendable component to remove
194   */
195  public static synchronized void remove(Sendable sendable) {
196    Component component = components.remove(sendable);
197    if (component != null && isEnabled()) {
198      component.m_builder.stopLiveWindowMode();
199    }
200  }
201
202  /**
203   * Enable telemetry for a single component.
204   *
205   * @param sendable component
206   */
207  public static synchronized void enableTelemetry(Sendable sendable) {
208    // Re-enable global setting in case disableAllTelemetry() was called.
209    telemetryEnabled = true;
210    Component component = components.get(sendable);
211    if (component != null) {
212      component.m_telemetryEnabled = true;
213    }
214  }
215
216  /**
217   * Disable telemetry for a single component.
218   *
219   * @param sendable component
220   */
221  public static synchronized void disableTelemetry(Sendable sendable) {
222    Component component = components.get(sendable);
223    if (component != null) {
224      component.m_telemetryEnabled = false;
225    }
226  }
227
228  /**
229   * Disable ALL telemetry.
230   */
231  public static synchronized void disableAllTelemetry() {
232    telemetryEnabled = false;
233    for (Component component : components.values()) {
234      component.m_telemetryEnabled = false;
235    }
236  }
237
238  /**
239   * Tell all the sensors to update (send) their values.
240   *
241   * <p>Actuators are handled through callbacks on their value changing from the
242   * SmartDashboard widgets.
243   */
244  public static synchronized void updateValues() {
245    // Only do this if either LiveWindow mode or telemetry is enabled.
246    if (!liveWindowEnabled && !telemetryEnabled) {
247      return;
248    }
249
250    for (Component component : components.values()) {
251      if (component.m_sendable != null && component.m_parent == null
252          && (liveWindowEnabled || component.m_telemetryEnabled)) {
253        if (component.m_firstTime) {
254          // By holding off creating the NetworkTable entries, it allows the
255          // components to be redefined. This allows default sensor and actuator
256          // values to be created that are replaced with the custom names from
257          // users calling setName.
258          String name = component.m_sendable.getName();
259          if (name.isEmpty()) {
260            continue;
261          }
262          String subsystem = component.m_sendable.getSubsystem();
263          NetworkTable ssTable = liveWindowTable.getSubTable(subsystem);
264          NetworkTable table;
265          // Treat name==subsystem as top level of subsystem
266          if (name.equals(subsystem)) {
267            table = ssTable;
268          } else {
269            table = ssTable.getSubTable(name);
270          }
271          table.getEntry(".name").setString(name);
272          component.m_builder.setTable(table);
273          component.m_sendable.initSendable(component.m_builder);
274          ssTable.getEntry(".type").setString("LW Subsystem");
275
276          component.m_firstTime = false;
277        }
278
279        if (startLiveWindow) {
280          component.m_builder.startLiveWindowMode();
281        }
282        component.m_builder.updateTable();
283      }
284    }
285
286    startLiveWindow = false;
287  }
288}