Update LiveWindow to provide continuous telemetry. (#771)

LiveWindow.updateValues() is now called from IterativeRobotBase on every
loop iteration.  Telemetry for all WPILib classes is enabled by default;
it can be disabled for specific classes using LiveWindow.disableTelemetry(),
or all telemetry can be disabled using LiveWindow.disableAllTelemetry().

This necessitated changing the hook methodology into other classes to
be more property-based rather than each class providing multiple functions.
This had the benefit of reducing boilerplate and increasing consistency.

- Remove NamedSendable, add name to Sendable.

- Provide SendableBase abstract class.

- Deprecate LiveWindow addSensor/addActuator interfaces.

- Add LiveWindow support to drive classes.

- Add addChild() helper functions to Subsystem.

- Fix inheritance hierarchy.  Now only sensors inherit from SensorBase.
  Other devices inherit from some combination of SendableBase, ErrorBase, or
  nothing.
This commit is contained in:
Peter Johnson
2017-12-04 23:28:33 -08:00
committed by GitHub
parent 3befc7015b
commit f9bece2ffb
213 changed files with 3704 additions and 3758 deletions

View File

@@ -7,14 +7,15 @@
package edu.wpi.first.wpilibj.livewindow;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.HashMap;
import java.util.Map;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.command.Scheduler;
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
import edu.wpi.first.wpilibj.Sendable;
/**
@@ -23,42 +24,30 @@ import edu.wpi.first.wpilibj.command.Scheduler;
*/
public class LiveWindow {
private static Vector<LiveWindowSendable> sensors = new Vector<>();
// private static Vector actuators = new Vector();
private static Hashtable<LiveWindowSendable, LiveWindowComponent> components = new Hashtable<>();
private static NetworkTable livewindowTable;
private static NetworkTable statusTable;
private static NetworkTableEntry enabledEntry;
private static boolean liveWindowEnabled = false;
private static boolean firstTime = true;
/**
* Initialize all the LiveWindow elements the first time we enter LiveWindow mode. By holding off
* creating the NetworkTable entries, it allows them to be redefined before the first time in
* LiveWindow mode. This allows default sensor and actuator values to be created that are replaced
* with the custom names from users calling addActuator and addSensor.
*/
private static void initializeLiveWindowComponents() {
System.out.println("Initializing the components first time");
livewindowTable = NetworkTableInstance.getDefault().getTable("LiveWindow");
statusTable = livewindowTable.getSubTable(".status");
enabledEntry = statusTable.getEntry("LW Enabled");
for (Enumeration e = components.keys(); e.hasMoreElements(); ) {
LiveWindowSendable component = (LiveWindowSendable) e.nextElement();
LiveWindowComponent liveWindowComponent = components.get(component);
String subsystem = liveWindowComponent.getSubsystem();
String name = liveWindowComponent.getName();
System.out.println("Initializing table for '" + subsystem + "' '" + name + "'");
livewindowTable.getSubTable(subsystem).getEntry(".type").setString("LW Subsystem");
NetworkTable table = livewindowTable.getSubTable(subsystem).getSubTable(name);
table.getEntry(".type").setString(component.getSmartDashboardType());
table.getEntry(".name").setString(name);
table.getEntry(".subsystem").setString(subsystem);
component.initTable(table);
if (liveWindowComponent.isSensor()) {
sensors.addElement(component);
}
private static class Component {
Component(Sendable sendable, Sendable parent) {
m_sendable = sendable;
m_parent = parent;
}
final Sendable m_sendable;
Sendable m_parent;
final SendableBuilderImpl m_builder = new SendableBuilderImpl();
boolean m_firstTime = true;
boolean m_telemetryEnabled = true;
}
private static final Map<Object, Component> components = new HashMap<>();
private static final NetworkTable liveWindowTable =
NetworkTableInstance.getDefault().getTable("LiveWindow");
private static final NetworkTable statusTable = liveWindowTable.getSubTable(".status");
private static final NetworkTableEntry enabledEntry = statusTable.getEntry("LW Enabled");
private static boolean startLiveWindow = false;
private static boolean liveWindowEnabled = false;
private static boolean telemetryEnabled = true;
public static synchronized boolean isEnabled() {
return liveWindowEnabled;
}
/**
@@ -69,28 +58,21 @@ public class LiveWindow {
* themselves when they get rescheduled. This prevents arms from starting to move around, etc.
* after a period of adjusting them in LiveWindow mode.
*/
public static void setEnabled(boolean enabled) {
public static synchronized void setEnabled(boolean enabled) {
if (liveWindowEnabled != enabled) {
Scheduler scheduler = Scheduler.getInstance();
if (enabled) {
System.out.println("Starting live window mode.");
if (firstTime) {
initializeLiveWindowComponents();
firstTime = false;
}
Scheduler.getInstance().disable();
Scheduler.getInstance().removeAll();
for (Enumeration e = components.keys(); e.hasMoreElements(); ) {
LiveWindowSendable component = (LiveWindowSendable) e.nextElement();
component.startLiveWindowMode();
}
scheduler.disable();
scheduler.removeAll();
} else {
System.out.println("stopping live window mode.");
for (Enumeration e = components.keys(); e.hasMoreElements(); ) {
LiveWindowSendable component = (LiveWindowSendable) e.nextElement();
component.stopLiveWindowMode();
for (Component component : components.values()) {
component.m_builder.stopLiveWindowMode();
}
Scheduler.getInstance().enable();
scheduler.enable();
}
startLiveWindow = enabled;
liveWindowEnabled = enabled;
enabledEntry.setBoolean(enabled);
}
@@ -98,7 +80,9 @@ public class LiveWindow {
/**
* The run method is called repeatedly to keep the values refreshed on the screen in test mode.
* @deprecated No longer required
*/
@Deprecated
public static void run() {
updateValues();
}
@@ -109,9 +93,12 @@ public class LiveWindow {
* @param subsystem The subsystem this component is part of.
* @param name The name of this component.
* @param component A LiveWindowSendable component that represents a sensor.
* @deprecated Use {@link Sendable#setName(String, String)} instead.
*/
public static void addSensor(String subsystem, String name, LiveWindowSendable component) {
components.put(component, new LiveWindowComponent(subsystem, name, true));
@Deprecated
public static synchronized void addSensor(String subsystem, String name, Sendable component) {
add(component);
component.setName(subsystem, name);
}
/**
@@ -121,13 +108,12 @@ public class LiveWindow {
* @param moduleType A string indicating the type of the module used in the naming (above)
* @param channel The channel number the device is connected to
* @param component A reference to the object being added
* @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int)} instead.
*/
public static void addSensor(String moduleType, int channel, LiveWindowSendable component) {
addSensor("Ungrouped", moduleType + "[" + channel + "]", component);
if (sensors.contains(component)) {
sensors.removeElement(component);
}
sensors.addElement(component);
@Deprecated
public static void addSensor(String moduleType, int channel, Sendable component) {
add(component);
component.setName("Ungrouped", moduleType + "[" + channel + "]");
}
/**
@@ -136,9 +122,12 @@ public class LiveWindow {
* @param subsystem The subsystem this component is part of.
* @param name The name of this component.
* @param component A LiveWindowSendable component that represents a actuator.
* @deprecated Use {@link Sendable#setName(String, String)} instead.
*/
public static void addActuator(String subsystem, String name, LiveWindowSendable component) {
components.put(component, new LiveWindowComponent(subsystem, name, false));
@Deprecated
public static synchronized void addActuator(String subsystem, String name, Sendable component) {
add(component);
component.setName(subsystem, name);
}
/**
@@ -148,9 +137,12 @@ public class LiveWindow {
* @param moduleType A string that defines the module name in the label for the value
* @param channel The channel number the device is plugged into (usually PWM)
* @param component The reference to the object being added
* @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int)} instead.
*/
public static void addActuator(String moduleType, int channel, LiveWindowSendable component) {
addActuator("Ungrouped", moduleType + "[" + channel + "]", component);
@Deprecated
public static void addActuator(String moduleType, int channel, Sendable component) {
add(component);
component.setName("Ungrouped", moduleType + "[" + channel + "]");
}
/**
@@ -161,22 +153,137 @@ public class LiveWindow {
* @param moduleNumber The number of the particular module type
* @param channel The channel number the device is plugged into (usually PWM)
* @param component The reference to the object being added
* @deprecated Use {@link edu.wpi.first.wpilibj.SensorBase#setName(String, int, int)} instead.
*/
@Deprecated
public static void addActuator(String moduleType, int moduleNumber, int channel,
LiveWindowSendable component) {
addActuator("Ungrouped", moduleType + "[" + moduleNumber + "," + channel + "]", component);
Sendable component) {
add(component);
component.setName("Ungrouped", moduleType + "[" + moduleNumber + "," + channel + "]");
}
/**
* Puts all sensor values on the live window.
* Add a component to the LiveWindow.
*
* @param sendable component to add
*/
private static void updateValues() {
// TODO: gross - needs to be sped up
for (int i = 0; i < sensors.size(); i++) {
LiveWindowSendable lws = sensors.elementAt(i);
lws.updateTable();
public static synchronized void add(Sendable sendable) {
components.putIfAbsent(sendable, new Component(sendable, null));
}
/**
* Add a child component to a component.
*
* @param parent parent component
* @param child child component
*/
public static synchronized void addChild(Sendable parent, Object child) {
Component component = components.get(child);
if (component == null) {
component = new Component(null, parent);
components.put(child, component);
} else {
component.m_parent = parent;
}
// TODO: Add actuators?
// TODO: Add better rate limiting.
component.m_telemetryEnabled = false;
}
/**
* Remove a component from the LiveWindow.
*
* @param sendable component to remove
*/
public static synchronized void remove(Sendable sendable) {
Component component = components.remove(sendable);
if (component != null && isEnabled()) {
component.m_builder.stopLiveWindowMode();
}
}
/**
* Enable telemetry for a single component.
*
* @param sendable component
*/
public static synchronized void enableTelemetry(Sendable sendable) {
// Re-enable global setting in case disableAllTelemetry() was called.
telemetryEnabled = true;
Component component = components.get(sendable);
if (component != null) {
component.m_telemetryEnabled = true;
}
}
/**
* Disable telemetry for a single component.
*
* @param sendable component
*/
public static synchronized void disableTelemetry(Sendable sendable) {
Component component = components.get(sendable);
if (component != null) {
component.m_telemetryEnabled = false;
}
}
/**
* Disable ALL telemetry.
*/
public static synchronized void disableAllTelemetry() {
telemetryEnabled = false;
for (Component component : components.values()) {
component.m_telemetryEnabled = false;
}
}
/**
* Tell all the sensors to update (send) their values.
*
* <p>Actuators are handled through callbacks on their value changing from the
* SmartDashboard widgets.
*/
public static synchronized void updateValues() {
// Only do this if either LiveWindow mode or telemetry is enabled.
if (!liveWindowEnabled && !telemetryEnabled) {
return;
}
for (Component component : components.values()) {
if (component.m_sendable != null && component.m_parent == null
&& (liveWindowEnabled || component.m_telemetryEnabled)) {
if (component.m_firstTime) {
// By holding off creating the NetworkTable entries, it allows the
// components to be redefined. This allows default sensor and actuator
// values to be created that are replaced with the custom names from
// users calling setName.
String name = component.m_sendable.getName();
if (name.isEmpty()) {
continue;
}
String subsystem = component.m_sendable.getSubsystem();
NetworkTable ssTable = liveWindowTable.getSubTable(subsystem);
NetworkTable table;
// Treat name==subsystem as top level of subsystem
if (name.equals(subsystem)) {
table = ssTable;
} else {
table = ssTable.getSubTable(name);
}
table.getEntry(".name").setString(name);
component.m_builder.setTable(table);
component.m_sendable.initSendable(component.m_builder);
ssTable.getEntry(".type").setString("LW Subsystem");
component.m_firstTime = false;
}
if (startLiveWindow) {
component.m_builder.startLiveWindowMode();
}
component.m_builder.updateTable();
}
}
startLiveWindow = false;
}
}

View File

@@ -1,41 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2016-2017 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.livewindow;
/**
* A LiveWindow component is a device (sensor or actuator) that should be added to the
* SmartDashboard in test mode. The components are cached until the first time the robot enters Test
* mode. This allows the components to be inserted, then renamed.
*/
/*
* This class is intentionally package private.
*/
class LiveWindowComponent {
String m_subsystem;
String m_name;
boolean m_isSensor;
LiveWindowComponent(String subsystem, String name, boolean isSensor) {
m_subsystem = subsystem;
m_name = name;
m_isSensor = isSensor;
}
public String getName() {
return m_name;
}
public String getSubsystem() {
return m_subsystem;
}
public boolean isSensor() {
return m_isSensor;
}
}

View File

@@ -1,31 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2008-2017 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.livewindow;
import edu.wpi.first.wpilibj.Sendable;
/**
* Live Window Sendable is a special type of object sendable to the live window.
*/
public interface LiveWindowSendable extends Sendable {
/**
* Update the table for this sendable object with the latest values.
*/
void updateTable();
/**
* Start having this sendable object automatically respond to value changes reflect the value on
* the table.
*/
void startLiveWindowMode();
/**
* Stop having this sendable object automatically respond to value changes.
*/
void stopLiveWindowMode();
}