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.livewindow;
006
007import edu.wpi.first.networktables.BooleanPublisher;
008import edu.wpi.first.networktables.NetworkTable;
009import edu.wpi.first.networktables.NetworkTableInstance;
010import edu.wpi.first.networktables.StringPublisher;
011import edu.wpi.first.networktables.StringTopic;
012import edu.wpi.first.util.sendable.Sendable;
013import edu.wpi.first.util.sendable.SendableRegistry;
014import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
015
016/**
017 * The LiveWindow class is the public interface for putting sensors and actuators on the LiveWindow.
018 */
019public final class LiveWindow {
020  private static class Component implements AutoCloseable {
021    @Override
022    public void close() {
023      if (m_namePub != null) {
024        m_namePub.close();
025        m_namePub = null;
026      }
027      if (m_typePub != null) {
028        m_typePub.close();
029        m_typePub = null;
030      }
031    }
032
033    boolean m_firstTime = true;
034    boolean m_telemetryEnabled;
035    StringPublisher m_namePub;
036    StringPublisher m_typePub;
037  }
038
039  private static final int dataHandle = SendableRegistry.getDataHandle();
040  private static final NetworkTable liveWindowTable =
041      NetworkTableInstance.getDefault().getTable("LiveWindow");
042  private static final NetworkTable statusTable = liveWindowTable.getSubTable(".status");
043  private static final BooleanPublisher enabledPub =
044      statusTable.getBooleanTopic("LW Enabled").publish();
045  private static boolean startLiveWindow;
046  private static boolean liveWindowEnabled;
047  private static boolean telemetryEnabled;
048
049  private static Runnable enabledListener;
050  private static Runnable disabledListener;
051
052  static {
053    SendableRegistry.setLiveWindowBuilderFactory(SendableBuilderImpl::new);
054    enabledPub.set(false);
055  }
056
057  private static Component getOrAdd(Sendable sendable) {
058    Component data = (Component) SendableRegistry.getData(sendable, dataHandle);
059    if (data == null) {
060      data = new Component();
061      SendableRegistry.setData(sendable, dataHandle, data);
062    }
063    return data;
064  }
065
066  private LiveWindow() {
067    throw new UnsupportedOperationException("This is a utility class!");
068  }
069
070  /**
071   * Sets function to be called when LiveWindow is enabled.
072   *
073   * @param runnable function (or null for none)
074   */
075  public static synchronized void setEnabledListener(Runnable runnable) {
076    enabledListener = runnable;
077  }
078
079  /**
080   * Sets function to be called when LiveWindow is disabled.
081   *
082   * @param runnable function (or null for none)
083   */
084  public static synchronized void setDisabledListener(Runnable runnable) {
085    disabledListener = runnable;
086  }
087
088  /**
089   * Returns true if LiveWindow is enabled.
090   *
091   * @return True if LiveWindow is enabled.
092   */
093  public static synchronized boolean isEnabled() {
094    return liveWindowEnabled;
095  }
096
097  /**
098   * Set the enabled state of LiveWindow.
099   *
100   * <p>If it's being enabled, turn off the scheduler and remove all the commands from the queue and
101   * enable all the components registered for LiveWindow. If it's being disabled, stop all the
102   * registered components and re-enable the scheduler.
103   *
104   * <p>TODO: add code to disable PID loops when enabling LiveWindow. The commands should re-enable
105   * the PID loops themselves when they get rescheduled. This prevents arms from starting to move
106   * around, etc. after a period of adjusting them in LiveWindow mode.
107   *
108   * @param enabled True to enable LiveWindow.
109   */
110  public static synchronized void setEnabled(boolean enabled) {
111    if (liveWindowEnabled != enabled) {
112      startLiveWindow = enabled;
113      liveWindowEnabled = enabled;
114      updateValues(); // Force table generation now to make sure everything is defined
115      if (enabled) {
116        System.out.println("Starting live window mode.");
117        if (enabledListener != null) {
118          enabledListener.run();
119        }
120      } else {
121        System.out.println("stopping live window mode.");
122        SendableRegistry.foreachLiveWindow(
123            dataHandle, cbdata -> ((SendableBuilderImpl) cbdata.builder).stopLiveWindowMode());
124        if (disabledListener != null) {
125          disabledListener.run();
126        }
127      }
128      enabledPub.set(enabled);
129    }
130  }
131
132  /**
133   * Enable telemetry for a single component.
134   *
135   * @param sendable component
136   */
137  public static synchronized void enableTelemetry(Sendable sendable) {
138    // Re-enable global setting in case disableAllTelemetry() was called.
139    telemetryEnabled = true;
140    getOrAdd(sendable).m_telemetryEnabled = true;
141  }
142
143  /**
144   * Disable telemetry for a single component.
145   *
146   * @param sendable component
147   */
148  public static synchronized void disableTelemetry(Sendable sendable) {
149    getOrAdd(sendable).m_telemetryEnabled = false;
150  }
151
152  /** Disable ALL telemetry. */
153  public static synchronized void disableAllTelemetry() {
154    telemetryEnabled = false;
155    SendableRegistry.foreachLiveWindow(
156        dataHandle,
157        cbdata -> {
158          if (cbdata.data == null) {
159            cbdata.data = new Component();
160          }
161          ((Component) cbdata.data).m_telemetryEnabled = false;
162        });
163  }
164
165  /** Enable ALL telemetry. */
166  public static synchronized void enableAllTelemetry() {
167    telemetryEnabled = true;
168    SendableRegistry.foreachLiveWindow(
169        dataHandle,
170        cbdata -> {
171          if (cbdata.data == null) {
172            cbdata.data = new Component();
173          }
174          ((Component) cbdata.data).m_telemetryEnabled = true;
175        });
176  }
177
178  /**
179   * Tell all the sensors to update (send) their values.
180   *
181   * <p>Actuators are handled through callbacks on their value changing from the SmartDashboard
182   * widgets.
183   */
184  public static synchronized void updateValues() {
185    // Only do this if either LiveWindow mode or telemetry is enabled.
186    if (!liveWindowEnabled && !telemetryEnabled) {
187      return;
188    }
189
190    SendableRegistry.foreachLiveWindow(
191        dataHandle,
192        cbdata -> {
193          if (cbdata.sendable == null || cbdata.parent != null) {
194            return;
195          }
196
197          if (cbdata.data == null) {
198            cbdata.data = new Component();
199          }
200
201          Component component = (Component) cbdata.data;
202
203          if (!liveWindowEnabled && !component.m_telemetryEnabled) {
204            return;
205          }
206
207          if (component.m_firstTime) {
208            // By holding off creating the NetworkTable entries, it allows the
209            // components to be redefined. This allows default sensor and actuator
210            // values to be created that are replaced with the custom names from
211            // users calling setName.
212            if (cbdata.name.isEmpty()) {
213              return;
214            }
215            NetworkTable ssTable = liveWindowTable.getSubTable(cbdata.subsystem);
216            NetworkTable table;
217            // Treat name==subsystem as top level of subsystem
218            if (cbdata.name.equals(cbdata.subsystem)) {
219              table = ssTable;
220            } else {
221              table = ssTable.getSubTable(cbdata.name);
222            }
223            component.m_namePub = new StringTopic(table.getTopic(".name")).publish();
224            component.m_namePub.set(cbdata.name);
225            ((SendableBuilderImpl) cbdata.builder).setTable(table);
226            cbdata.sendable.initSendable(cbdata.builder);
227            component.m_typePub = new StringTopic(ssTable.getTopic(".type")).publish();
228            component.m_typePub.set("LW Subsystem");
229
230            component.m_firstTime = false;
231          }
232
233          if (startLiveWindow) {
234            ((SendableBuilderImpl) cbdata.builder).startLiveWindowMode();
235          }
236          cbdata.builder.update();
237        });
238
239    startLiveWindow = false;
240  }
241}