[ntcore] NetworkTables 4 (#3217)

This commit is contained in:
Peter Johnson
2022-10-08 10:01:31 -07:00
committed by GitHub
parent 90cfa00115
commit 77301b126c
380 changed files with 34573 additions and 22095 deletions

View File

@@ -10,9 +10,10 @@ import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.hal.SimEnum;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.DoubleTopic;
import edu.wpi.first.networktables.NTSendable;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.interfaces.Accelerometer;
import java.nio.ByteBuffer;
@@ -231,15 +232,18 @@ public class ADXL345_I2C implements Accelerometer, NTSendable, AutoCloseable {
@Override
public void initSendable(NTSendableBuilder builder) {
builder.setSmartDashboardType("3AxisAccelerometer");
NetworkTableEntry entryX = builder.getEntry("X");
NetworkTableEntry entryY = builder.getEntry("Y");
NetworkTableEntry entryZ = builder.getEntry("Z");
DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
builder.addCloseable(pubX);
builder.addCloseable(pubY);
builder.addCloseable(pubZ);
builder.setUpdateTable(
() -> {
AllAxes data = getAccelerations();
entryX.setDouble(data.XAxis);
entryY.setDouble(data.YAxis);
entryZ.setDouble(data.ZAxis);
pubX.set(data.XAxis);
pubY.set(data.YAxis);
pubZ.set(data.ZAxis);
});
}
}

View File

@@ -10,9 +10,10 @@ import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.hal.SimEnum;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.DoubleTopic;
import edu.wpi.first.networktables.NTSendable;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.interfaces.Accelerometer;
import java.nio.ByteBuffer;
@@ -234,15 +235,18 @@ public class ADXL345_SPI implements Accelerometer, NTSendable, AutoCloseable {
@Override
public void initSendable(NTSendableBuilder builder) {
builder.setSmartDashboardType("3AxisAccelerometer");
NetworkTableEntry entryX = builder.getEntry("X");
NetworkTableEntry entryY = builder.getEntry("Y");
NetworkTableEntry entryZ = builder.getEntry("Z");
DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
builder.addCloseable(pubX);
builder.addCloseable(pubY);
builder.addCloseable(pubZ);
builder.setUpdateTable(
() -> {
AllAxes data = getAccelerations();
entryX.setDouble(data.XAxis);
entryY.setDouble(data.YAxis);
entryZ.setDouble(data.ZAxis);
pubX.set(data.XAxis);
pubY.set(data.YAxis);
pubZ.set(data.ZAxis);
});
}
}

View File

@@ -9,9 +9,10 @@ import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.SimDevice;
import edu.wpi.first.hal.SimDouble;
import edu.wpi.first.hal.SimEnum;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.DoubleTopic;
import edu.wpi.first.networktables.NTSendable;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.interfaces.Accelerometer;
import java.nio.ByteBuffer;
@@ -263,15 +264,18 @@ public class ADXL362 implements Accelerometer, NTSendable, AutoCloseable {
@Override
public void initSendable(NTSendableBuilder builder) {
builder.setSmartDashboardType("3AxisAccelerometer");
NetworkTableEntry entryX = builder.getEntry("X");
NetworkTableEntry entryY = builder.getEntry("Y");
NetworkTableEntry entryZ = builder.getEntry("Z");
DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish();
DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish();
DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish();
builder.addCloseable(pubX);
builder.addCloseable(pubY);
builder.addCloseable(pubZ);
builder.setUpdateTable(
() -> {
AllAxes data = getAccelerations();
entryX.setDouble(data.XAxis);
entryY.setDouble(data.YAxis);
entryZ.setDouble(data.ZAxis);
pubX.set(data.XAxis);
pubY.set(data.YAxis);
pubZ.set(data.ZAxis);
});
}
}

View File

@@ -8,9 +8,11 @@ import edu.wpi.first.hal.AllianceStationID;
import edu.wpi.first.hal.ControlWord;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.MatchInfoData;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.datalog.BooleanArrayLogEntry;
import edu.wpi.first.util.datalog.BooleanLogEntry;
@@ -83,15 +85,15 @@ public final class DriverStation {
@SuppressWarnings("MemberName")
private static class MatchDataSender {
NetworkTable table;
NetworkTableEntry typeMetadata;
NetworkTableEntry gameSpecificMessage;
NetworkTableEntry eventName;
NetworkTableEntry matchNumber;
NetworkTableEntry replayNumber;
NetworkTableEntry matchType;
NetworkTableEntry alliance;
NetworkTableEntry station;
NetworkTableEntry controlWord;
StringPublisher typeMetadata;
StringPublisher gameSpecificMessage;
StringPublisher eventName;
IntegerPublisher matchNumber;
IntegerPublisher replayNumber;
IntegerPublisher matchType;
BooleanPublisher alliance;
IntegerPublisher station;
IntegerPublisher controlWord;
boolean oldIsRedAlliance = true;
int oldStationNumber = 1;
String oldEventName = "";
@@ -103,24 +105,24 @@ public final class DriverStation {
MatchDataSender() {
table = NetworkTableInstance.getDefault().getTable("FMSInfo");
typeMetadata = table.getEntry(".type");
typeMetadata.forceSetString("FMSInfo");
gameSpecificMessage = table.getEntry("GameSpecificMessage");
gameSpecificMessage.forceSetString("");
eventName = table.getEntry("EventName");
eventName.forceSetString("");
matchNumber = table.getEntry("MatchNumber");
matchNumber.forceSetDouble(0);
replayNumber = table.getEntry("ReplayNumber");
replayNumber.forceSetDouble(0);
matchType = table.getEntry("MatchType");
matchType.forceSetDouble(0);
alliance = table.getEntry("IsRedAlliance");
alliance.forceSetBoolean(true);
station = table.getEntry("StationNumber");
station.forceSetDouble(1);
controlWord = table.getEntry("FMSControlData");
controlWord.forceSetDouble(0);
typeMetadata = table.getStringTopic(".type").publish();
typeMetadata.set("FMSInfo");
gameSpecificMessage = table.getStringTopic("GameSpecificMessage").publish();
gameSpecificMessage.set("");
eventName = table.getStringTopic("EventName").publish();
eventName.set("");
matchNumber = table.getIntegerTopic("MatchNumber").publish();
matchNumber.set(0);
replayNumber = table.getIntegerTopic("ReplayNumber").publish();
replayNumber.set(0);
matchType = table.getIntegerTopic("MatchType").publish();
matchType.set(0);
alliance = table.getBooleanTopic("IsRedAlliance").publish();
alliance.set(true);
station = table.getIntegerTopic("StationNumber").publish();
station.set(1);
controlWord = table.getIntegerTopic("FMSControlData").publish();
controlWord.set(0);
}
private void sendMatchData() {
@@ -173,35 +175,35 @@ public final class DriverStation {
currentControlWord = HAL.nativeGetControlWord();
if (oldIsRedAlliance != isRedAlliance) {
alliance.setBoolean(isRedAlliance);
alliance.set(isRedAlliance);
oldIsRedAlliance = isRedAlliance;
}
if (oldStationNumber != stationNumber) {
station.setDouble(stationNumber);
station.set(stationNumber);
oldStationNumber = stationNumber;
}
if (!oldEventName.equals(currentEventName)) {
eventName.setString(currentEventName);
eventName.set(currentEventName);
oldEventName = currentEventName;
}
if (!oldGameSpecificMessage.equals(currentGameSpecificMessage)) {
gameSpecificMessage.setString(currentGameSpecificMessage);
gameSpecificMessage.set(currentGameSpecificMessage);
oldGameSpecificMessage = currentGameSpecificMessage;
}
if (currentMatchNumber != oldMatchNumber) {
matchNumber.setDouble(currentMatchNumber);
matchNumber.set(currentMatchNumber);
oldMatchNumber = currentMatchNumber;
}
if (currentReplayNumber != oldReplayNumber) {
replayNumber.setDouble(currentReplayNumber);
replayNumber.set(currentReplayNumber);
oldReplayNumber = currentReplayNumber;
}
if (currentMatchType != oldMatchType) {
matchType.setDouble(currentMatchType);
matchType.set(currentMatchType);
oldMatchType = currentMatchType;
}
if (currentControlWord != oldControlWord) {
controlWord.setDouble(currentControlWord);
controlWord.set(currentControlWord);
oldControlWord = currentControlWord;
}
}

View File

@@ -65,7 +65,7 @@ public abstract class IterativeRobotBase extends RobotBase {
private Mode m_lastMode = Mode.kNone;
private final double m_period;
private final Watchdog m_watchdog;
private boolean m_ntFlushEnabled;
private boolean m_ntFlushEnabled = true;
/**
* Constructor for IterativeRobotBase.
@@ -235,7 +235,7 @@ public abstract class IterativeRobotBase extends RobotBase {
public void testExit() {}
/**
* Enables or disables flushing NetworkTables every loop iteration. By default, this is disabled.
* Enables or disables flushing NetworkTables every loop iteration. By default, this is enabled.
*
* @param enabled True to enable, false to disable
*/
@@ -343,7 +343,7 @@ public abstract class IterativeRobotBase extends RobotBase {
// Flush NetworkTables
if (m_ntFlushEnabled) {
NetworkTableInstance.getDefault().flush();
NetworkTableInstance.getDefault().flushLocal();
}
// Warn on loop time overruns

View File

@@ -8,10 +8,13 @@ import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.hal.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.networktables.EntryListenerFlags;
import edu.wpi.first.networktables.MultiSubscriber;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.networktables.TopicListener;
import edu.wpi.first.networktables.TopicListenerFlags;
import java.util.Collection;
/**
@@ -31,20 +34,51 @@ public final class Preferences {
/** The Preferences table name. */
private static final String TABLE_NAME = "Preferences";
/** The network table. */
private static final NetworkTable m_table;
private static NetworkTable m_table;
private static StringPublisher m_typePublisher;
private static MultiSubscriber m_tableSubscriber;
private static TopicListener m_listener;
/** Creates a preference class. */
private Preferences() {}
static {
m_table = NetworkTableInstance.getDefault().getTable(TABLE_NAME);
m_table.getEntry(".type").setString("RobotPreferences");
setNetworkTableInstance(NetworkTableInstance.getDefault());
HAL.report(tResourceType.kResourceType_Preferences, 0);
}
/**
* Set the NetworkTable instance used for entries. For testing purposes; use with caution.
*
* @param inst NetworkTable instance
*/
public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
m_table = inst.getTable(TABLE_NAME);
if (m_typePublisher != null) {
m_typePublisher.close();
}
m_typePublisher = m_table.getStringTopic(".type").publish();
m_typePublisher.set("RobotPreferences");
// Subscribe to all Preferences; this ensures we get the latest values
// ahead of a getter call.
if (m_tableSubscriber != null) {
m_tableSubscriber.close();
}
m_tableSubscriber = new MultiSubscriber(inst, new String[] {m_table.getPath() + "/"});
// Listener to set all Preferences values to persistent
// (for backwards compatibility with old dashboards).
m_table.addEntryListener(
(table, key, entry, value, flags) -> entry.setPersistent(),
EntryListenerFlags.kImmediate | EntryListenerFlags.kNew);
HAL.report(tResourceType.kResourceType_Preferences, 0);
if (m_listener != null) {
m_listener.close();
}
m_listener =
new TopicListener(
m_table.getInstance(),
new String[] {m_table.getPath() + "/"},
TopicListenerFlags.kImmediate | TopicListenerFlags.kPublish,
event -> event.info.getTopic().setPersistent(true));
}
/**
@@ -219,7 +253,9 @@ public final class Preferences {
* @param key the key
*/
public static void remove(String key) {
m_table.delete(key);
NetworkTableEntry entry = m_table.getEntry(key);
entry.clearPersistent();
entry.unpublish();
}
/** Remove all preferences. */

View File

@@ -14,6 +14,7 @@ import edu.wpi.first.hal.HALUtil;
import edu.wpi.first.math.MathShared;
import edu.wpi.first.math.MathSharedStore;
import edu.wpi.first.math.MathUsageId;
import edu.wpi.first.networktables.MultiSubscriber;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.wpilibj.livewindow.LiveWindow;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
@@ -38,6 +39,8 @@ public abstract class RobotBase implements AutoCloseable {
// This is usually 1, but it is best to make sure
private static long m_threadId = -1;
private final MultiSubscriber m_suball;
private static void setupCameraServerShared() {
CameraServerShared shared =
new CameraServerShared() {
@@ -142,12 +145,27 @@ public abstract class RobotBase implements AutoCloseable {
setupCameraServerShared();
setupMathShared();
inst.setNetworkIdentity("Robot");
// subscribe to "" to force persistent values to progagate to local
m_suball = new MultiSubscriber(inst, new String[] {""});
if (isReal()) {
inst.startServer("/home/lvuser/networktables.ini");
inst.startServer("/home/lvuser/networktables.json");
} else {
inst.startServer();
}
inst.getTable("LiveWindow").getSubTable(".status").getEntry("LW Enabled").setBoolean(false);
// wait for the NT server to actually start
try {
int count = 0;
while ((inst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) {
Thread.sleep(10);
count++;
if (count > 100) {
throw new InterruptedException();
}
}
} catch (InterruptedException ex) {
System.err.println("timed out while waiting for NT server to start");
}
LiveWindow.setEnabled(false);
Shuffleboard.disableActuatorWidgets();
@@ -158,7 +176,9 @@ public abstract class RobotBase implements AutoCloseable {
}
@Override
public void close() {}
public void close() {
m_suball.close();
}
/**
* Get the current runtime type.

View File

@@ -0,0 +1,71 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.wpilibj.event;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.BooleanSubscriber;
import edu.wpi.first.networktables.BooleanTopic;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
/** This class provides an easy way to link NetworkTables boolean topics to callback actions. */
public class NetworkBooleanEvent extends BooleanEvent {
/**
* Creates a new event with the given boolean topic determining whether it is active.
*
* @param loop the loop that polls this event
* @param topic The boolean topic that contains the value
*/
public NetworkBooleanEvent(EventLoop loop, BooleanTopic topic) {
this(loop, topic.subscribe(false));
}
/**
* Creates a new event with the given boolean subscriber determining whether it is active.
*
* @param loop the loop that polls this event
* @param sub The boolean subscriber that provides the value
*/
public NetworkBooleanEvent(EventLoop loop, BooleanSubscriber sub) {
super(loop, () -> sub.getTopic().getInstance().isConnected() && sub.get());
requireNonNullParam(sub, "sub", "NetworkBooleanEvent");
}
/**
* Creates a new event with the given boolean topic determining whether it is active.
*
* @param loop the loop that polls this event
* @param table The NetworkTable that contains the topic
* @param topicName The topic name within the table that contains the value
*/
public NetworkBooleanEvent(EventLoop loop, NetworkTable table, String topicName) {
this(loop, table.getBooleanTopic(topicName));
}
/**
* Creates a new event with the given boolean topic determining whether it is active.
*
* @param loop the loop that polls this event
* @param tableName The NetworkTable name that contains the topic
* @param topicName The topic name within the table that contains the value
*/
public NetworkBooleanEvent(EventLoop loop, String tableName, String topicName) {
this(loop, NetworkTableInstance.getDefault(), tableName, topicName);
}
/**
* Creates a new event with the given boolean topic determining whether it is active.
*
* @param loop the loop that polls this event
* @param inst The NetworkTable instance to use
* @param tableName The NetworkTable that contains the topic
* @param topicName The topic name within the table that contains the value
*/
public NetworkBooleanEvent(
EventLoop loop, NetworkTableInstance inst, String tableName, String topicName) {
this(loop, inst.getTable(tableName), topicName);
}
}

View File

@@ -4,9 +4,11 @@
package edu.wpi.first.wpilibj.livewindow;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.networktables.StringTopic;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableRegistry;
import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
@@ -15,16 +17,31 @@ import edu.wpi.first.wpilibj.smartdashboard.SendableBuilderImpl;
* The LiveWindow class is the public interface for putting sensors and actuators on the LiveWindow.
*/
public final class LiveWindow {
private static class Component {
private static class Component implements AutoCloseable {
@Override
public void close() {
if (m_namePub != null) {
m_namePub.close();
m_namePub = null;
}
if (m_typePub != null) {
m_typePub.close();
m_typePub = null;
}
}
boolean m_firstTime = true;
boolean m_telemetryEnabled;
StringPublisher m_namePub;
StringPublisher m_typePub;
}
private static final int dataHandle = SendableRegistry.getDataHandle();
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 final BooleanPublisher enabledPub =
statusTable.getBooleanTopic("LW Enabled").publish();
private static boolean startLiveWindow;
private static boolean liveWindowEnabled;
private static boolean telemetryEnabled;
@@ -34,6 +51,7 @@ public final class LiveWindow {
static {
SendableRegistry.setLiveWindowBuilderFactory(() -> new SendableBuilderImpl());
enabledPub.set(false);
}
private static Component getOrAdd(Sendable sendable) {
@@ -95,7 +113,7 @@ public final class LiveWindow {
disabledListener.run();
}
}
enabledEntry.setBoolean(enabled);
enabledPub.set(enabled);
}
}
@@ -177,10 +195,12 @@ public final class LiveWindow {
} else {
table = ssTable.getSubTable(cbdata.name);
}
table.getEntry(".name").setString(cbdata.name);
component.m_namePub = new StringTopic(table.getTopic(".name")).publish();
component.m_namePub.set(cbdata.name);
((SendableBuilderImpl) cbdata.builder).setTable(table);
cbdata.sendable.initSendable(cbdata.builder);
ssTable.getEntry(".type").setString("LW Subsystem");
component.m_typePub = new StringTopic(ssTable.getTopic(".type")).publish();
component.m_typePub.set("LW Subsystem");
component.m_firstTime = false;
}

View File

@@ -12,7 +12,7 @@ import edu.wpi.first.wpilibj.interfaces.Accelerometer.Range;
* <p>For example, setting a number to be displayed with a slider:
*
* <pre>{@code
* NetworkTableEntry example = Shuffleboard.getTab("My Tab")
* GenericEntry example = Shuffleboard.getTab("My Tab")
* .add("My Number", 0)
* .withWidget(BuiltInWidgets.kNumberSlider)
* .withProperties(Map.of("min", 0, "max", 1))

View File

@@ -4,7 +4,10 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.GenericPublisher;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableType;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.util.ArrayList;
@@ -18,6 +21,7 @@ import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** A helper class for Shuffleboard containers to handle common child operations. */
@@ -68,6 +72,11 @@ final class ContainerHelper {
}
SimpleWidget add(String title, Object defaultValue) {
Objects.requireNonNull(defaultValue, "Default value cannot be null");
return add(title, NetworkTableType.getStringFromObject(defaultValue), defaultValue);
}
SimpleWidget add(String title, String typeString, Object defaultValue) {
Objects.requireNonNull(title, "Title cannot be null");
Objects.requireNonNull(defaultValue, "Default value cannot be null");
checkTitle(title);
@@ -75,43 +84,72 @@ final class ContainerHelper {
SimpleWidget widget = new SimpleWidget(m_container, title);
m_components.add(widget);
widget.getEntry().setDefaultValue(defaultValue);
widget.getEntry(typeString).setDefaultValue(defaultValue);
return widget;
}
SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier, NetworkTableEntry::setString);
return addSupplied(title, "string", valueSupplier, GenericPublisher::setString);
}
SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier) {
return addDouble(title, valueSupplier);
}
SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier::getAsDouble, NetworkTableEntry::setDouble);
return addSupplied(title, "double", valueSupplier::getAsDouble, GenericPublisher::setDouble);
}
SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, "float", valueSupplier::getAsFloat, GenericPublisher::setFloat);
}
SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, "int", valueSupplier::getAsLong, GenericPublisher::setInteger);
}
SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier::getAsBoolean, NetworkTableEntry::setBoolean);
return addSupplied(title, "boolean", valueSupplier::getAsBoolean, GenericPublisher::setBoolean);
}
SuppliedValueWidget<String[]> addStringArray(String title, Supplier<String[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier, NetworkTableEntry::setStringArray);
return addSupplied(title, "string[]", valueSupplier, GenericPublisher::setStringArray);
}
SuppliedValueWidget<double[]> addDoubleArray(String title, Supplier<double[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier, NetworkTableEntry::setDoubleArray);
return addSupplied(title, "double[]", valueSupplier, GenericPublisher::setDoubleArray);
}
SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, "float[]", valueSupplier, GenericPublisher::setFloatArray);
}
SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, "int[]", valueSupplier, GenericPublisher::setIntegerArray);
}
SuppliedValueWidget<boolean[]> addBooleanArray(String title, Supplier<boolean[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier, NetworkTableEntry::setBooleanArray);
return addSupplied(title, "boolean[]", valueSupplier, GenericPublisher::setBooleanArray);
}
SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return addRaw(title, "raw", valueSupplier);
}
SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
precheck(title, valueSupplier);
return addSupplied(title, valueSupplier, NetworkTableEntry::setRaw);
return addSupplied(title, typeString, valueSupplier, GenericPublisher::setRaw);
}
private void precheck(String title, Object valueSupplier) {
@@ -121,8 +159,12 @@ final class ContainerHelper {
}
private <T> SuppliedValueWidget<T> addSupplied(
String title, Supplier<T> supplier, BiConsumer<NetworkTableEntry, T> setter) {
SuppliedValueWidget<T> widget = new SuppliedValueWidget<>(m_container, title, supplier, setter);
String title,
String typeString,
Supplier<T> supplier,
BiConsumer<GenericPublisher, T> setter) {
SuppliedValueWidget<T> widget =
new SuppliedValueWidget<>(m_container, title, typeString, supplier, setter);
m_components.add(widget);
return widget;
}

View File

@@ -4,9 +4,10 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.wpilibj.DriverStation;
/** Controls Shuffleboard recordings via NetworkTables. */
@@ -16,30 +17,31 @@ final class RecordingController {
private static final String kRecordingFileNameFormatKey = kRecordingTableName + "FileNameFormat";
private static final String kEventMarkerTableName = kRecordingTableName + "events";
private final NetworkTableEntry m_recordingControlEntry;
private final NetworkTableEntry m_recordingFileNameFormatEntry;
private final BooleanPublisher m_recordingControlEntry;
private final StringPublisher m_recordingFileNameFormatEntry;
private final NetworkTable m_eventsTable;
RecordingController(NetworkTableInstance ntInstance) {
m_recordingControlEntry = ntInstance.getEntry(kRecordingControlKey);
m_recordingFileNameFormatEntry = ntInstance.getEntry(kRecordingFileNameFormatKey);
m_recordingControlEntry = ntInstance.getBooleanTopic(kRecordingControlKey).publish();
m_recordingFileNameFormatEntry =
ntInstance.getStringTopic(kRecordingFileNameFormatKey).publish();
m_eventsTable = ntInstance.getTable(kEventMarkerTableName);
}
public void startRecording() {
m_recordingControlEntry.setBoolean(true);
m_recordingControlEntry.set(true);
}
public void stopRecording() {
m_recordingControlEntry.setBoolean(false);
m_recordingControlEntry.set(false);
}
public void setRecordingFileNameFormat(String format) {
m_recordingFileNameFormatEntry.setString(format);
m_recordingFileNameFormatEntry.set(format);
}
public void clearRecordingFileNameFormat() {
m_recordingFileNameFormatEntry.delete();
m_recordingFileNameFormatEntry.set("");
}
public void addEventMarker(String name, String description, EventImportance importance) {

View File

@@ -5,7 +5,10 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.cscore.VideoSource;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringArrayPublisher;
import edu.wpi.first.networktables.StringArrayTopic;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableBuilder;
import edu.wpi.first.util.sendable.SendableRegistry;
@@ -19,7 +22,14 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
private static Map<String, SendableCameraWrapper> m_wrappers = new WeakHashMap<>();
private static NetworkTable m_table;
static {
setNetworkTableInstance(NetworkTableInstance.getDefault());
}
private final String m_uri;
private StringArrayPublisher m_streams;
/**
* Creates a new sendable wrapper. Private constructor to avoid direct instantiation with multiple
@@ -36,6 +46,19 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
m_uri = kProtocol + cameraName;
}
private SendableCameraWrapper(String cameraName, String[] cameraUrls) {
this(cameraName);
StringArrayTopic streams = new StringArrayTopic(m_table.getTopic(cameraName + "/streams"));
if (streams.exists()) {
throw new IllegalStateException(
"A camera is already being streamed with the name '" + cameraName + "'");
}
m_streams = streams.publish();
m_streams.set(cameraUrls);
}
/** Clears all cached wrapper objects. This should only be used in tests. */
static void clearWrappers() {
m_wrappers.clear();
@@ -44,6 +67,18 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
@Override
public void close() {
SendableRegistry.remove(this);
if (m_streams != null) {
m_streams.close();
}
}
/*
* Sets NetworkTable instance used for camera publisher entries.
*
* @param inst NetworkTable instance
*/
public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
m_table = inst.getTable("CameraPublisher");
}
/**
@@ -89,15 +124,7 @@ public final class SendableCameraWrapper implements Sendable, AutoCloseable {
Objects.requireNonNull(cameraUrls[i], "Camera URL at index " + i + " was null");
}
String streams = "/CameraPublisher/" + cameraName + "/streams";
if (NetworkTableInstance.getDefault().getEntries(streams, 0).length != 0) {
throw new IllegalStateException(
"A camera is already being streamed with the name '" + cameraName + "'");
}
NetworkTableInstance.getDefault().getEntry(streams).setStringArray(cameraUrls);
SendableCameraWrapper wrapper = new SendableCameraWrapper(cameraName);
SendableCameraWrapper wrapper = new SendableCameraWrapper(cameraName, cameraUrls);
m_wrappers.put(cameraName, wrapper);
return wrapper;
}

View File

@@ -16,7 +16,7 @@ import edu.wpi.first.networktables.NetworkTableInstance;
* <p>For example, displaying a boolean entry with a toggle button:
*
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Toggle Button")
* .getEntry();
@@ -25,7 +25,7 @@ import edu.wpi.first.networktables.NetworkTableInstance;
* <p>Changing the colors of the boolean box:
*
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .add("My Boolean", false)
* .withWidget("Boolean Box")
* .withProperties(Map.of("colorWhenTrue", "green", "colorWhenFalse", "maroon"))
@@ -36,7 +36,7 @@ import edu.wpi.first.networktables.NetworkTableInstance;
* the layout has already been generated by a previously defined entry.
*
* <pre>{@code
* NetworkTableEntry myBoolean = Shuffleboard.getTab("Example Tab")
* GenericEntry myBoolean = Shuffleboard.getTab("Example Tab")
* .getLayout("List", "Example List")
* .add("My Boolean", false)
* .withWidget("Toggle Button")

View File

@@ -115,21 +115,21 @@ public abstract class ShuffleboardComponent<C extends ShuffleboardComponent<C>>
}
// Component type
if (getType() == null) {
metaTable.getEntry("PreferredComponent").delete();
metaTable.getEntry("PreferredComponent").unpublish();
} else {
metaTable.getEntry("PreferredComponent").forceSetString(getType());
metaTable.getEntry("PreferredComponent").setString(getType());
}
// Tile size
if (m_width <= 0 || m_height <= 0) {
metaTable.getEntry("Size").delete();
metaTable.getEntry("Size").unpublish();
} else {
metaTable.getEntry("Size").setDoubleArray(new double[] {m_width, m_height});
}
// Tile position
if (m_column < 0 || m_row < 0) {
metaTable.getEntry("Position").delete();
metaTable.getEntry("Position").unpublish();
} else {
metaTable.getEntry("Position").setDoubleArray(new double[] {m_column, m_row});
}

View File

@@ -5,11 +5,14 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.cscore.VideoSource;
import edu.wpi.first.networktables.NetworkTableType;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** Common interface for objects that can contain shuffleboard components. */
@@ -122,6 +125,19 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
*/
SimpleWidget add(String title, Object defaultValue);
/**
* Adds a widget to this container to display the given data.
*
* @param title the title of the widget
* @param typeString the NT type string
* @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, String typeString, Object defaultValue);
/**
* Adds a widget to this container to display a video stream.
*
@@ -162,6 +178,45 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
*/
SuppliedValueWidget<Double> addNumber(String title, DoubleSupplier valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
@@ -201,6 +256,32 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
*/
SuppliedValueWidget<double[]> addDoubleArray(String title, Supplier<double[]> valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier);
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
@@ -225,7 +306,24 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier);
default SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return addRaw(title, "raw", valueSupplier);
}
/**
* Adds a widget to this container. The widget will display the data provided by the value
* supplier. Changes made on the dashboard will not propagate to the widget object, and will be
* overridden by values from the value supplier.
*
* @param title the title of the widget
* @param typeString the NT type string for the value
* @param valueSupplier the supplier for values
* @return a widget to display data
* @throws IllegalArgumentException if a widget already exists in this container with the given
* title
*/
SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier);
/**
* Adds a widget to this container to display a simple piece of data. Unlike {@link #add(String,
@@ -240,8 +338,25 @@ public interface ShuffleboardContainer extends ShuffleboardValue {
* @see #add(String, Object) add(String title, Object defaultValue)
*/
default SimpleWidget addPersistent(String title, Object defaultValue) {
return addPersistent(title, NetworkTableType.getStringFromObject(defaultValue), defaultValue);
}
/**
* 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 typeString the NT type string
* @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, String typeString, Object defaultValue) {
SimpleWidget widget = add(title, defaultValue);
widget.getEntry().setPersistent();
widget.getEntry(typeString).getTopic().setPersistent(true);
return widget;
}
}

View File

@@ -9,8 +9,8 @@ import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.hal.FRCNetComm.tResourceType;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.StringPublisher;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
@@ -21,7 +21,7 @@ final class ShuffleboardInstance implements ShuffleboardRoot {
private boolean m_tabsChanged = false; // NOPMD redundant field initializer
private final NetworkTable m_rootTable;
private final NetworkTable m_rootMetaTable;
private final NetworkTableEntry m_selectedTabEntry;
private final StringPublisher m_selectedTabPub;
/**
* Creates a new Shuffleboard instance.
@@ -32,7 +32,7 @@ final class ShuffleboardInstance implements ShuffleboardRoot {
requireNonNullParam(ntInstance, "ntInstance", "ShuffleboardInstance");
m_rootTable = ntInstance.getTable(Shuffleboard.kBaseTableName);
m_rootMetaTable = m_rootTable.getSubTable(".metadata");
m_selectedTabEntry = m_rootMetaTable.getEntry("Selected");
m_selectedTabPub = m_rootMetaTable.getStringTopic("Selected").publish();
HAL.report(tResourceType.kResourceType_Shuffleboard, 0);
}
@@ -51,7 +51,7 @@ final class ShuffleboardInstance implements ShuffleboardRoot {
if (m_tabsChanged) {
String[] tabTitles =
m_tabs.values().stream().map(ShuffleboardTab::getTitle).toArray(String[]::new);
m_rootMetaTable.getEntry("Tabs").forceSetStringArray(tabTitles);
m_rootMetaTable.getEntry("Tabs").setStringArray(tabTitles);
m_tabsChanged = false;
}
for (ShuffleboardTab tab : m_tabs.values()) {
@@ -72,12 +72,12 @@ final class ShuffleboardInstance implements ShuffleboardRoot {
@Override
public void selectTab(int index) {
m_selectedTabEntry.forceSetDouble(index);
selectTab(Integer.toString(index));
}
@Override
public void selectTab(String title) {
m_selectedTabEntry.forceSetString(title);
m_selectedTabPub.set(title);
}
/**

View File

@@ -7,10 +7,12 @@ package edu.wpi.first.wpilibj.shuffleboard;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/** A layout in a Shuffleboard tab. Layouts can contain widgets and other layouts. */
@@ -52,6 +54,11 @@ public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout
return m_helper.add(title, defaultValue);
}
@Override
public SimpleWidget add(String title, String typeString, Object defaultValue) {
return m_helper.add(title, typeString, defaultValue);
}
@Override
public SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
return m_helper.addString(title, valueSupplier);
@@ -62,6 +69,21 @@ public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout
return m_helper.addNumber(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
return m_helper.addDouble(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
return m_helper.addFloat(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
return m_helper.addInteger(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
return m_helper.addBoolean(title, valueSupplier);
@@ -79,6 +101,16 @@ public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout
return m_helper.addDoubleArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
return m_helper.addFloatArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
return m_helper.addIntegerArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<boolean[]> addBooleanArray(
String title, Supplier<boolean[]> valueSupplier) {
@@ -86,8 +118,9 @@ public class ShuffleboardLayout extends ShuffleboardComponent<ShuffleboardLayout
}
@Override
public SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, valueSupplier);
public SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, typeString, valueSupplier);
}
@Override

View File

@@ -5,10 +5,12 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.util.function.FloatSupplier;
import edu.wpi.first.util.sendable.Sendable;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
@@ -66,6 +68,11 @@ public final class ShuffleboardTab implements ShuffleboardContainer {
return m_helper.add(title, defaultValue);
}
@Override
public SimpleWidget add(String title, String typeString, Object defaultValue) {
return m_helper.add(title, typeString, defaultValue);
}
@Override
public SuppliedValueWidget<String> addString(String title, Supplier<String> valueSupplier) {
return m_helper.addString(title, valueSupplier);
@@ -76,6 +83,21 @@ public final class ShuffleboardTab implements ShuffleboardContainer {
return m_helper.addNumber(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Double> addDouble(String title, DoubleSupplier valueSupplier) {
return m_helper.addDouble(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Float> addFloat(String title, FloatSupplier valueSupplier) {
return m_helper.addFloat(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Long> addInteger(String title, LongSupplier valueSupplier) {
return m_helper.addInteger(title, valueSupplier);
}
@Override
public SuppliedValueWidget<Boolean> addBoolean(String title, BooleanSupplier valueSupplier) {
return m_helper.addBoolean(title, valueSupplier);
@@ -93,6 +115,16 @@ public final class ShuffleboardTab implements ShuffleboardContainer {
return m_helper.addDoubleArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<float[]> addFloatArray(String title, Supplier<float[]> valueSupplier) {
return m_helper.addFloatArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<long[]> addIntegerArray(String title, Supplier<long[]> valueSupplier) {
return m_helper.addIntegerArray(title, valueSupplier);
}
@Override
public SuppliedValueWidget<boolean[]> addBooleanArray(
String title, Supplier<boolean[]> valueSupplier) {
@@ -100,8 +132,9 @@ public final class ShuffleboardTab implements ShuffleboardContainer {
}
@Override
public SuppliedValueWidget<byte[]> addRaw(String title, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, valueSupplier);
public SuppliedValueWidget<byte[]> addRaw(
String title, String typeString, Supplier<byte[]> valueSupplier) {
return m_helper.addRaw(title, typeString, valueSupplier);
}
@Override

View File

@@ -4,12 +4,13 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.GenericEntry;
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;
public final class SimpleWidget extends ShuffleboardWidget<SimpleWidget> implements AutoCloseable {
private String m_typeString = "";
private GenericEntry m_entry;
SimpleWidget(ShuffleboardContainer parent, String title) {
super(parent, title);
@@ -20,18 +21,39 @@ public final class SimpleWidget extends ShuffleboardWidget<SimpleWidget> {
*
* @return The NetworkTable entry that contains the data for this widget.
*/
public NetworkTableEntry getEntry() {
public GenericEntry getEntry() {
if (m_entry == null) {
forceGenerate();
}
return m_entry;
}
/**
* Gets the NetworkTable entry that contains the data for this widget.
*
* @param typeString NetworkTable type string
* @return The NetworkTable entry that contains the data for this widget.
*/
public GenericEntry getEntry(String typeString) {
if (m_entry == null) {
m_typeString = typeString;
forceGenerate();
}
return m_entry;
}
@Override
public void close() {
if (m_entry != null) {
m_entry.close();
}
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
if (m_entry == null) {
m_entry = parentTable.getEntry(getTitle());
m_entry = parentTable.getTopic(getTitle()).getGenericEntry(m_typeString);
}
}

View File

@@ -4,8 +4,10 @@
package edu.wpi.first.wpilibj.shuffleboard;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.BooleanTopic;
import edu.wpi.first.networktables.GenericPublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -14,34 +16,52 @@ import java.util.function.Supplier;
*
* @param <T> the type of values in the widget
*/
public final class SuppliedValueWidget<T> extends ShuffleboardWidget<SuppliedValueWidget<T>> {
public final class SuppliedValueWidget<T> extends ShuffleboardWidget<SuppliedValueWidget<T>>
implements AutoCloseable {
private final String m_typeString;
private final Supplier<T> m_supplier;
private final BiConsumer<NetworkTableEntry, T> m_setter;
private final BiConsumer<GenericPublisher, T> m_setter;
private BooleanPublisher m_controllablePub;
private GenericPublisher m_entry;
/**
* Package-private constructor for use by the Shuffleboard API.
*
* @param parent the parent container for the widget
* @param title the title of the widget
* @param typeString the NetworkTables string type
* @param supplier the supplier for values to place in the NetworkTable entry
* @param setter the function for placing values in the NetworkTable entry
*/
SuppliedValueWidget(
ShuffleboardContainer parent,
String title,
String typeString,
Supplier<T> supplier,
BiConsumer<NetworkTableEntry, T> setter) {
BiConsumer<GenericPublisher, T> setter) {
super(parent, title);
this.m_supplier = supplier;
this.m_setter = setter;
m_typeString = typeString;
m_supplier = supplier;
m_setter = setter;
}
@Override
public void buildInto(NetworkTable parentTable, NetworkTable metaTable) {
buildMetadata(metaTable);
metaTable.getEntry("Controllable").setBoolean(false);
m_controllablePub = new BooleanTopic(metaTable.getTopic("Controllable")).publish();
m_controllablePub.set(false);
NetworkTableEntry entry = parentTable.getEntry(getTitle());
m_setter.accept(entry, m_supplier.get());
m_entry = parentTable.getTopic(getTitle()).genericPublish(m_typeString);
m_setter.accept(m_entry, m_supplier.get());
}
@Override
public void close() {
if (m_controllablePub != null) {
m_controllablePub.close();
}
if (m_entry != null) {
m_entry.close();
}
}
}

View File

@@ -29,7 +29,7 @@ import java.util.List;
* using the getObject() function. Other objects can also have multiple poses (which will show the
* object at multiple locations).
*/
public class Field2d implements NTSendable {
public class Field2d implements NTSendable, AutoCloseable {
/** Constructor. */
public Field2d() {
FieldObject2d obj = new FieldObject2d("Robot");
@@ -38,6 +38,13 @@ public class Field2d implements NTSendable {
SendableRegistry.add(this, "Field");
}
@Override
public void close() {
for (FieldObject2d obj : m_objects) {
obj.close();
}
}
/**
* Set the robot pose from a Pose object.
*
@@ -83,7 +90,7 @@ public class Field2d implements NTSendable {
m_objects.add(obj);
if (m_table != null) {
synchronized (obj) {
obj.m_entry = m_table.getEntry(name);
obj.m_entry = m_table.getDoubleArrayTopic(name).getEntry(new double[] {});
}
}
return obj;
@@ -106,7 +113,7 @@ public class Field2d implements NTSendable {
m_table = builder.getTable();
for (FieldObject2d obj : m_objects) {
synchronized (obj) {
obj.m_entry = m_table.getEntry(obj.m_name);
obj.m_entry = m_table.getDoubleArrayTopic(obj.m_name).getEntry(new double[] {});
obj.updateEntry(true);
}
}

View File

@@ -8,14 +8,12 @@ import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.trajectory.Trajectory;
import edu.wpi.first.networktables.NetworkTableEntry;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import edu.wpi.first.networktables.DoubleArrayEntry;
import java.util.ArrayList;
import java.util.List;
/** Game field object on a Field2d. */
public class FieldObject2d {
public class FieldObject2d implements AutoCloseable {
/**
* Package-local constructor.
*
@@ -25,6 +23,11 @@ public class FieldObject2d {
m_name = name;
}
@Override
public void close() {
m_entry.close();
}
/**
* Set the pose from a Pose object.
*
@@ -116,39 +119,20 @@ public class FieldObject2d {
return;
}
if (m_poses.size() < (255 / 3)) {
double[] arr = new double[m_poses.size() * 3];
int ndx = 0;
for (Pose2d pose : m_poses) {
Translation2d translation = pose.getTranslation();
arr[ndx + 0] = translation.getX();
arr[ndx + 1] = translation.getY();
arr[ndx + 2] = pose.getRotation().getDegrees();
ndx += 3;
}
double[] arr = new double[m_poses.size() * 3];
int ndx = 0;
for (Pose2d pose : m_poses) {
Translation2d translation = pose.getTranslation();
arr[ndx + 0] = translation.getX();
arr[ndx + 1] = translation.getY();
arr[ndx + 2] = pose.getRotation().getDegrees();
ndx += 3;
}
if (setDefault) {
m_entry.setDefaultDoubleArray(arr);
} else {
m_entry.setDoubleArray(arr);
}
if (setDefault) {
m_entry.setDefault(arr);
} else {
// send as raw array of doubles if too big for NT array
ByteBuffer output = ByteBuffer.allocate(m_poses.size() * 3 * 8);
output.order(ByteOrder.BIG_ENDIAN);
for (Pose2d pose : m_poses) {
Translation2d translation = pose.getTranslation();
output.putDouble(translation.getX());
output.putDouble(translation.getY());
output.putDouble(pose.getRotation().getDegrees());
}
if (setDefault) {
m_entry.setDefaultRaw(output.array());
} else {
m_entry.forceSetRaw(output.array());
}
m_entry.set(arr);
}
}
@@ -157,7 +141,7 @@ public class FieldObject2d {
return;
}
double[] arr = m_entry.getDoubleArray((double[]) null);
double[] arr = m_entry.get((double[]) null);
if (arr != null) {
if ((arr.length % 3) != 0) {
return;
@@ -167,31 +151,10 @@ public class FieldObject2d {
for (int i = 0; i < arr.length; i += 3) {
m_poses.add(new Pose2d(arr[i], arr[i + 1], Rotation2d.fromDegrees(arr[i + 2])));
}
} else {
// read as raw array of doubles
byte[] data = m_entry.getRaw((byte[]) null);
if (data == null) {
return;
}
// must be triples of doubles
if ((data.length % (3 * 8)) != 0) {
return;
}
ByteBuffer input = ByteBuffer.wrap(data);
input.order(ByteOrder.BIG_ENDIAN);
m_poses.clear();
for (int i = 0; i < (data.length / (3 * 8)); i++) {
double x = input.getDouble();
double y = input.getDouble();
double rot = input.getDouble();
m_poses.add(new Pose2d(x, y, Rotation2d.fromDegrees(rot)));
}
}
}
String m_name;
NetworkTableEntry m_entry;
DoubleArrayEntry m_entry;
private final List<Pose2d> m_poses = new ArrayList<>();
}

View File

@@ -4,9 +4,11 @@
package edu.wpi.first.wpilibj.smartdashboard;
import edu.wpi.first.networktables.DoubleArrayPublisher;
import edu.wpi.first.networktables.NTSendable;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.wpilibj.util.Color8Bit;
import java.util.HashMap;
import java.util.Map;
@@ -23,12 +25,13 @@ import java.util.Map.Entry;
* @see MechanismLigament2d
* @see MechanismRoot2d
*/
public final class Mechanism2d implements NTSendable {
private static final String kBackgroundColor = "backgroundColor";
public final class Mechanism2d implements NTSendable, AutoCloseable {
private NetworkTable m_table;
private final Map<String, MechanismRoot2d> m_roots;
private final double[] m_dims = new double[2];
private String m_color;
private DoubleArrayPublisher m_dimsPub;
private StringPublisher m_colorPub;
/**
* Create a new Mechanism2d with the given dimensions and default color (dark blue).
@@ -58,6 +61,19 @@ public final class Mechanism2d implements NTSendable {
setBackgroundColor(backgroundColor);
}
@Override
public void close() {
if (m_dimsPub != null) {
m_dimsPub.close();
}
if (m_colorPub != null) {
m_colorPub.close();
}
for (MechanismRoot2d root : m_roots.values()) {
root.close();
}
}
/**
* Get or create a root in this Mechanism2d with the given name and position.
*
@@ -88,9 +104,9 @@ public final class Mechanism2d implements NTSendable {
* @param color the new color
*/
public synchronized void setBackgroundColor(Color8Bit color) {
this.m_color = color.toHexString();
if (m_table != null) {
m_table.getEntry(kBackgroundColor).setString(m_color);
m_color = color.toHexString();
if (m_colorPub != null) {
m_colorPub.set(m_color);
}
}
@@ -99,8 +115,16 @@ public final class Mechanism2d implements NTSendable {
builder.setSmartDashboardType("Mechanism2d");
synchronized (this) {
m_table = builder.getTable();
m_table.getEntry("dims").setDoubleArray(m_dims);
m_table.getEntry(kBackgroundColor).setString(m_color);
if (m_dimsPub != null) {
m_dimsPub.close();
}
m_dimsPub = m_table.getDoubleArrayTopic("dims").publish();
m_dimsPub.set(m_dims);
if (m_colorPub != null) {
m_colorPub.close();
}
m_colorPub = m_table.getStringTopic("backgroundColor").publish();
m_colorPub.set(m_color);
for (Entry<String, MechanismRoot2d> entry : m_roots.entrySet()) {
String name = entry.getKey();
MechanismRoot2d root = entry.getValue();

View File

@@ -5,8 +5,10 @@
package edu.wpi.first.wpilibj.smartdashboard;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.networktables.DoubleEntry;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.StringEntry;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.wpilibj.util.Color8Bit;
/**
@@ -16,14 +18,15 @@ import edu.wpi.first.wpilibj.util.Color8Bit;
* @see Mechanism2d
*/
public class MechanismLigament2d extends MechanismObject2d {
private StringPublisher m_typePub;
private double m_angle;
private NetworkTableEntry m_angleEntry;
private DoubleEntry m_angleEntry;
private String m_color;
private NetworkTableEntry m_colorEntry;
private StringEntry m_colorEntry;
private double m_length;
private NetworkTableEntry m_lengthEntry;
private DoubleEntry m_lengthEntry;
private double m_weight;
private NetworkTableEntry m_weightEntry;
private DoubleEntry m_weightEntry;
/**
* Create a new ligament.
@@ -54,6 +57,26 @@ public class MechanismLigament2d extends MechanismObject2d {
this(name, length, angle, 10, new Color8Bit(235, 137, 52));
}
@Override
public void close() {
super.close();
if (m_typePub != null) {
m_typePub.close();
}
if (m_angleEntry != null) {
m_angleEntry.close();
}
if (m_colorEntry != null) {
m_colorEntry.close();
}
if (m_lengthEntry != null) {
m_lengthEntry.close();
}
if (m_weightEntry != null) {
m_weightEntry.close();
}
}
/**
* Set the ligament's angle relative to its parent.
*
@@ -61,7 +84,9 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized void setAngle(double degrees) {
m_angle = degrees;
flush();
if (m_angleEntry != null) {
m_angleEntry.set(degrees);
}
}
/**
@@ -80,7 +105,7 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized double getAngle() {
if (m_angleEntry != null) {
m_angle = m_angleEntry.getDouble(0.0);
m_angle = m_angleEntry.get();
}
return m_angle;
}
@@ -92,7 +117,9 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized void setLength(double length) {
m_length = length;
flush();
if (m_lengthEntry != null) {
m_lengthEntry.set(length);
}
}
/**
@@ -102,7 +129,7 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized double getLength() {
if (m_lengthEntry != null) {
m_length = m_lengthEntry.getDouble(0.0);
m_length = m_lengthEntry.get();
}
return m_length;
}
@@ -114,7 +141,9 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized void setColor(Color8Bit color) {
m_color = String.format("#%02X%02X%02X", color.red, color.green, color.blue);
flush();
if (m_colorEntry != null) {
m_colorEntry.set(m_color);
}
}
/**
@@ -124,7 +153,7 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized Color8Bit getColor() {
if (m_colorEntry != null) {
m_color = m_colorEntry.getString("");
m_color = m_colorEntry.get();
}
int r = 0;
int g = 0;
@@ -150,7 +179,9 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized void setLineWeight(double weight) {
m_weight = weight;
flush();
if (m_weightEntry != null) {
m_weightEntry.set(weight);
}
}
/**
@@ -160,34 +191,41 @@ public class MechanismLigament2d extends MechanismObject2d {
*/
public synchronized double getLineWeight() {
if (m_weightEntry != null) {
m_weight = m_weightEntry.getDouble(0.0);
m_weight = m_weightEntry.get();
}
return m_weight;
}
@Override
protected void updateEntries(NetworkTable table) {
table.getEntry(".type").setString("line");
m_angleEntry = table.getEntry("angle");
m_lengthEntry = table.getEntry("length");
m_colorEntry = table.getEntry("color");
m_weightEntry = table.getEntry("weight");
flush();
}
if (m_typePub != null) {
m_typePub.close();
}
m_typePub = table.getStringTopic(".type").publish();
m_typePub.set("line");
/** Flush latest data to NT. */
private void flush() {
if (m_angleEntry != null) {
m_angleEntry.setDouble(m_angle);
m_angleEntry.close();
}
m_angleEntry = table.getDoubleTopic("angle").getEntry(0.0);
m_angleEntry.set(m_angle);
if (m_lengthEntry != null) {
m_lengthEntry.setDouble(m_length);
m_lengthEntry.close();
}
m_lengthEntry = table.getDoubleTopic("length").getEntry(0.0);
m_lengthEntry.set(m_length);
if (m_colorEntry != null) {
m_colorEntry.setString(m_color);
m_colorEntry.close();
}
m_colorEntry = table.getStringTopic("color").getEntry("");
m_colorEntry.set(m_color);
if (m_weightEntry != null) {
m_weightEntry.setDouble(m_weight);
m_weightEntry.close();
}
m_weightEntry = table.getDoubleTopic("weight").getEntry(0.0);
m_weightEntry.set(m_weight);
}
}

View File

@@ -16,7 +16,7 @@ import java.util.Map;
*
* @see Mechanism2d
*/
public abstract class MechanismObject2d {
public abstract class MechanismObject2d implements AutoCloseable {
/** Relative to parent. */
private final String m_name;
@@ -32,6 +32,13 @@ public abstract class MechanismObject2d {
m_name = name;
}
@Override
public void close() {
for (MechanismObject2d obj : m_objects.values()) {
obj.close();
}
}
/**
* Append a Mechanism object that is based on this one.
*

View File

@@ -4,8 +4,8 @@
package edu.wpi.first.wpilibj.smartdashboard;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import java.util.HashMap;
import java.util.Map;
@@ -19,14 +19,14 @@ import java.util.Map;
*
* <p>Append other nodes by using {@link #append(MechanismObject2d)}.
*/
public final class MechanismRoot2d {
public final class MechanismRoot2d implements AutoCloseable {
private final String m_name;
private NetworkTable m_table;
private final Map<String, MechanismObject2d> m_objects = new HashMap<>(1);
private double m_x;
private NetworkTableEntry m_xEntry;
private DoublePublisher m_xPub;
private double m_y;
private NetworkTableEntry m_yEntry;
private DoublePublisher m_yPub;
/**
* Package-private constructor for roots.
@@ -41,6 +41,19 @@ public final class MechanismRoot2d {
m_y = y;
}
@Override
public void close() {
if (m_xPub != null) {
m_xPub.close();
}
if (m_yPub != null) {
m_yPub.close();
}
for (MechanismObject2d obj : m_objects.values()) {
obj.close();
}
}
/**
* Append a Mechanism object that is based on this one.
*
@@ -75,8 +88,14 @@ public final class MechanismRoot2d {
synchronized void update(NetworkTable table) {
m_table = table;
m_xEntry = m_table.getEntry("x");
m_yEntry = m_table.getEntry("y");
if (m_xPub != null) {
m_xPub.close();
}
m_xPub = m_table.getDoubleTopic("x").publish();
if (m_yPub != null) {
m_yPub.close();
}
m_yPub = m_table.getDoubleTopic("y").publish();
flush();
for (MechanismObject2d obj : m_objects.values()) {
obj.update(m_table.getSubTable(obj.getName()));
@@ -88,11 +107,11 @@ public final class MechanismRoot2d {
}
private void flush() {
if (m_xEntry != null) {
m_xEntry.setDouble(m_x);
if (m_xPub != null) {
m_xPub.set(m_x);
}
if (m_yEntry != null) {
m_yEntry.setDouble(m_y);
if (m_yPub != null) {
m_yPub.set(m_y);
}
}
}

View File

@@ -4,58 +4,132 @@
package edu.wpi.first.wpilibj.smartdashboard;
import edu.wpi.first.networktables.EntryListenerFlags;
import edu.wpi.first.networktables.BooleanArrayPublisher;
import edu.wpi.first.networktables.BooleanArraySubscriber;
import edu.wpi.first.networktables.BooleanArrayTopic;
import edu.wpi.first.networktables.BooleanPublisher;
import edu.wpi.first.networktables.BooleanSubscriber;
import edu.wpi.first.networktables.BooleanTopic;
import edu.wpi.first.networktables.DoubleArrayPublisher;
import edu.wpi.first.networktables.DoubleArraySubscriber;
import edu.wpi.first.networktables.DoubleArrayTopic;
import edu.wpi.first.networktables.DoublePublisher;
import edu.wpi.first.networktables.DoubleSubscriber;
import edu.wpi.first.networktables.DoubleTopic;
import edu.wpi.first.networktables.FloatArrayPublisher;
import edu.wpi.first.networktables.FloatArraySubscriber;
import edu.wpi.first.networktables.FloatArrayTopic;
import edu.wpi.first.networktables.FloatPublisher;
import edu.wpi.first.networktables.FloatSubscriber;
import edu.wpi.first.networktables.FloatTopic;
import edu.wpi.first.networktables.IntegerArrayPublisher;
import edu.wpi.first.networktables.IntegerArraySubscriber;
import edu.wpi.first.networktables.IntegerArrayTopic;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.IntegerSubscriber;
import edu.wpi.first.networktables.IntegerTopic;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableValue;
import edu.wpi.first.networktables.Publisher;
import edu.wpi.first.networktables.RawPublisher;
import edu.wpi.first.networktables.RawSubscriber;
import edu.wpi.first.networktables.RawTopic;
import edu.wpi.first.networktables.StringArrayPublisher;
import edu.wpi.first.networktables.StringArraySubscriber;
import edu.wpi.first.networktables.StringArrayTopic;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.networktables.StringSubscriber;
import edu.wpi.first.networktables.StringTopic;
import edu.wpi.first.networktables.Subscriber;
import edu.wpi.first.networktables.Topic;
import edu.wpi.first.util.WPIUtilJNI;
import edu.wpi.first.util.function.BooleanConsumer;
import edu.wpi.first.util.function.FloatConsumer;
import edu.wpi.first.util.function.FloatSupplier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.DoubleSupplier;
import java.util.function.Function;
import java.util.function.LongConsumer;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
public class SendableBuilderImpl implements NTSendableBuilder {
private static class Property implements AutoCloseable {
Property(NetworkTable table, String key) {
m_entry = table.getEntry(key);
}
@Override
public void close() {
stopListener();
}
void startListener() {
if (m_entry.isValid() && m_listener == 0 && m_createListener != null) {
m_listener = m_createListener.apply(m_entry);
}
}
void stopListener() {
if (m_entry.isValid() && m_listener != 0) {
m_entry.removeListener(m_listener);
m_listener = 0;
}
}
final NetworkTableEntry m_entry;
int m_listener;
Consumer<NetworkTableEntry> m_update;
Function<NetworkTableEntry, Integer> m_createListener;
@FunctionalInterface
private interface TimedConsumer<T> {
void accept(T value, long time);
}
private final List<Property> m_properties = new ArrayList<>();
private static class Property<P extends Publisher, S extends Subscriber>
implements AutoCloseable {
@Override
@SuppressWarnings("PMD.AvoidCatchingGenericException")
public void close() {
try {
if (m_pub != null) {
m_pub.close();
}
if (m_sub != null) {
m_sub.close();
}
} catch (Exception e) {
// ignore
}
}
void update(boolean controllable, long time) {
if (controllable && m_sub != null && m_updateLocal != null) {
m_updateLocal.accept(m_sub);
} else if (m_pub != null && m_updateNetwork != null) {
m_updateNetwork.accept(m_pub, time);
}
}
P m_pub;
S m_sub;
TimedConsumer<P> m_updateNetwork;
Consumer<S> m_updateLocal;
}
private final List<Property<?, ?>> m_properties = new ArrayList<>();
private Runnable m_safeState;
private final List<Runnable> m_updateTables = new ArrayList<>();
private NetworkTable m_table;
private NetworkTableEntry m_controllableEntry;
private boolean m_controllable;
private boolean m_actuator;
private BooleanPublisher m_controllablePub;
private StringPublisher m_typePub;
private BooleanPublisher m_actuatorPub;
private final List<AutoCloseable> m_closeables = new ArrayList<>();
@Override
@SuppressWarnings("PMD.AvoidCatchingGenericException")
public void close() {
if (m_controllablePub != null) {
m_controllablePub.close();
}
if (m_typePub != null) {
m_typePub.close();
}
if (m_actuatorPub != null) {
m_actuatorPub.close();
}
for (Property<?, ?> property : m_properties) {
property.close();
}
for (AutoCloseable closeable : m_closeables) {
try {
closeable.close();
} catch (Exception e) {
// ignore
}
}
}
/**
* Set the network table. Must be called prior to any Add* functions being called.
*
@@ -63,7 +137,8 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
public void setTable(NetworkTable table) {
m_table = table;
m_controllableEntry = table.getEntry(".controllable");
m_controllablePub = table.getBooleanTopic(".controllable").publish();
m_controllablePub.setDefault(false);
}
/**
@@ -98,10 +173,9 @@ public class SendableBuilderImpl implements NTSendableBuilder {
/** Update the network table values by calling the getters for all properties. */
@Override
public void update() {
for (Property property : m_properties) {
if (property.m_update != null) {
property.m_update.accept(property.m_entry);
}
long time = WPIUtilJNI.now();
for (Property<?, ?> property : m_properties) {
property.update(m_controllable, time);
}
for (Runnable updateTable : m_updateTables) {
updateTable.run();
@@ -110,21 +184,17 @@ public class SendableBuilderImpl implements NTSendableBuilder {
/** Hook setters for all properties. */
public void startListeners() {
for (Property property : m_properties) {
property.startListener();
}
if (m_controllableEntry != null) {
m_controllableEntry.setBoolean(true);
m_controllable = true;
if (m_controllablePub != null) {
m_controllablePub.set(true);
}
}
/** Unhook setters for all properties. */
public void stopListeners() {
for (Property property : m_properties) {
property.stopListener();
}
if (m_controllableEntry != null) {
m_controllableEntry.setBoolean(false);
m_controllable = false;
if (m_controllablePub != null) {
m_controllablePub.set(false);
}
}
@@ -154,9 +224,17 @@ public class SendableBuilderImpl implements NTSendableBuilder {
@Override
public void clearProperties() {
stopListeners();
for (Property<?, ?> property : m_properties) {
property.close();
}
m_properties.clear();
}
@Override
public void addCloseable(AutoCloseable closeable) {
m_closeables.add(closeable);
}
/**
* Set the string representation of the named data type that will be used by the smart dashboard
* for this sendable.
@@ -165,7 +243,10 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
@Override
public void setSmartDashboardType(String type) {
m_table.getEntry(".type").setString(type);
if (m_typePub == null) {
m_typePub = m_table.getStringTopic(".type").publish();
}
m_typePub.set(type);
}
/**
@@ -176,7 +257,10 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
@Override
public void setActuator(boolean value) {
m_table.getEntry(".actuator").setBoolean(value);
if (m_actuatorPub == null) {
m_actuatorPub = m_table.getBooleanTopic(".actuator").publish();
}
m_actuatorPub.set(value);
m_actuator = value;
}
@@ -194,7 +278,7 @@ public class SendableBuilderImpl implements NTSendableBuilder {
/**
* Set the function that should be called to update the network table for things other than
* properties. Note this function is not passed the network table object; instead it should use
* the entry handles returned by getEntry().
* the topics returned by getTopic().
*
* @param func function
*/
@@ -211,8 +295,8 @@ public class SendableBuilderImpl implements NTSendableBuilder {
* @return Network table entry
*/
@Override
public NetworkTableEntry getEntry(String key) {
return m_table.getEntry(key);
public Topic getTopic(String key) {
return m_table.getTopic(key);
}
/**
@@ -224,23 +308,74 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
@Override
public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
Property property = new Property(m_table, key);
Property<BooleanPublisher, BooleanSubscriber> property = new Property<>();
BooleanTopic topic = m_table.getBooleanTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setBoolean(getter.getAsBoolean());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsBoolean(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isBoolean()) {
SmartDashboard.postListenerTask(
() -> setter.accept(event.value.getBoolean()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(false);
property.m_updateLocal =
sub -> {
for (boolean val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
/**
* Add an integer property.
*
* @param key property name
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
Property<IntegerPublisher, IntegerSubscriber> property = new Property<>();
IntegerTopic topic = m_table.getIntegerTopic(key);
if (getter != null) {
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsLong(), time);
}
if (setter != null) {
property.m_sub = topic.subscribe(0);
property.m_updateLocal =
sub -> {
for (long val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
/**
* Add a float property.
*
* @param key property name
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
Property<FloatPublisher, FloatSubscriber> property = new Property<>();
FloatTopic topic = m_table.getFloatTopic(key);
if (getter != null) {
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsFloat(), time);
}
if (setter != null) {
property.m_sub = topic.subscribe(0.0f);
property.m_updateLocal =
sub -> {
for (float val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -254,22 +389,20 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
@Override
public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
Property property = new Property(m_table, key);
Property<DoublePublisher, DoubleSubscriber> property = new Property<>();
DoubleTopic topic = m_table.getDoubleTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setDouble(getter.getAsDouble());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsDouble(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isDouble()) {
SmartDashboard.postListenerTask(() -> setter.accept(event.value.getDouble()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(0.0);
property.m_updateLocal =
sub -> {
for (double val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -283,22 +416,20 @@ public class SendableBuilderImpl implements NTSendableBuilder {
*/
@Override
public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
Property property = new Property(m_table, key);
Property<StringPublisher, StringSubscriber> property = new Property<>();
StringTopic topic = m_table.getStringTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setString(getter.get());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isString()) {
SmartDashboard.postListenerTask(() -> setter.accept(event.value.getString()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe("");
property.m_updateLocal =
sub -> {
for (String val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -313,23 +444,76 @@ public class SendableBuilderImpl implements NTSendableBuilder {
@Override
public void addBooleanArrayProperty(
String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
Property property = new Property(m_table, key);
Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>();
BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setBooleanArray(getter.get());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isBooleanArray()) {
SmartDashboard.postListenerTask(
() -> setter.accept(event.value.getBooleanArray()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(new boolean[] {});
property.m_updateLocal =
sub -> {
for (boolean[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
/**
* Add an integer array property.
*
* @param key property name
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addIntegerArrayProperty(
String key, Supplier<long[]> getter, Consumer<long[]> setter) {
Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>();
IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key);
if (getter != null) {
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_sub = topic.subscribe(new long[] {});
property.m_updateLocal =
sub -> {
for (long[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
/**
* Add a float array property.
*
* @param key property name
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addFloatArrayProperty(
String key, Supplier<float[]> getter, Consumer<float[]> setter) {
Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>();
FloatArrayTopic topic = m_table.getFloatArrayTopic(key);
if (getter != null) {
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_sub = topic.subscribe(new float[] {});
property.m_updateLocal =
sub -> {
for (float[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -344,23 +528,20 @@ public class SendableBuilderImpl implements NTSendableBuilder {
@Override
public void addDoubleArrayProperty(
String key, Supplier<double[]> getter, Consumer<double[]> setter) {
Property property = new Property(m_table, key);
Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>();
DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setDoubleArray(getter.get());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isDoubleArray()) {
SmartDashboard.postListenerTask(
() -> setter.accept(event.value.getDoubleArray()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(new double[] {});
property.m_updateLocal =
sub -> {
for (double[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -375,23 +556,20 @@ public class SendableBuilderImpl implements NTSendableBuilder {
@Override
public void addStringArrayProperty(
String key, Supplier<String[]> getter, Consumer<String[]> setter) {
Property property = new Property(m_table, key);
Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>();
StringArrayTopic topic = m_table.getStringArrayTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setStringArray(getter.get());
property.m_pub = topic.publish();
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isStringArray()) {
SmartDashboard.postListenerTask(
() -> setter.accept(event.value.getStringArray()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(new String[] {});
property.m_updateLocal =
sub -> {
for (String[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}
@@ -400,55 +578,27 @@ public class SendableBuilderImpl implements NTSendableBuilder {
* Add a raw property.
*
* @param key property name
* @param typeString type string
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addRawProperty(String key, Supplier<byte[]> getter, Consumer<byte[]> setter) {
Property property = new Property(m_table, key);
public void addRawProperty(
String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
Property<RawPublisher, RawSubscriber> property = new Property<>();
RawTopic topic = m_table.getRawTopic(key);
if (getter != null) {
property.m_update = entry -> entry.setRaw(getter.get());
property.m_pub = topic.publish(typeString);
property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
if (event.value.isRaw()) {
SmartDashboard.postListenerTask(() -> setter.accept(event.value.getRaw()));
}
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
}
m_properties.add(property);
}
/**
* Add a NetworkTableValue property.
*
* @param key property name
* @param getter getter function (returns current value)
* @param setter setter function (sets new value)
*/
@Override
public void addValueProperty(
String key, Supplier<NetworkTableValue> getter, Consumer<NetworkTableValue> setter) {
Property property = new Property(m_table, key);
if (getter != null) {
property.m_update = entry -> entry.setValue(getter.get());
}
if (setter != null) {
property.m_createListener =
entry ->
entry.addListener(
event -> {
SmartDashboard.postListenerTask(() -> setter.accept(event.value));
},
EntryListenerFlags.kImmediate
| EntryListenerFlags.kNew
| EntryListenerFlags.kUpdate);
property.m_sub = topic.subscribe(typeString, new byte[] {});
property.m_updateLocal =
sub -> {
for (byte[] val : sub.readQueueValues()) {
setter.accept(val);
}
};
}
m_properties.add(property);
}

View File

@@ -6,9 +6,12 @@ package edu.wpi.first.wpilibj.smartdashboard;
import static edu.wpi.first.wpilibj.util.ErrorMessages.requireNonNullParam;
import edu.wpi.first.networktables.IntegerPublisher;
import edu.wpi.first.networktables.IntegerTopic;
import edu.wpi.first.networktables.NTSendable;
import edu.wpi.first.networktables.NTSendableBuilder;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.StringPublisher;
import edu.wpi.first.networktables.StringTopic;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -56,6 +59,14 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
@Override
public void close() {
SendableRegistry.remove(this);
m_mutex.lock();
try {
for (StringPublisher pub : m_activePubs) {
pub.close();
}
} finally {
m_mutex.unlock();
}
}
/**
@@ -104,13 +115,15 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
}
private String m_selected;
private final List<NetworkTableEntry> m_activeEntries = new ArrayList<>();
private final List<StringPublisher> m_activePubs = new ArrayList<>();
private final ReentrantLock m_mutex = new ReentrantLock();
@Override
public void initSendable(NTSendableBuilder builder) {
builder.setSmartDashboardType("String Chooser");
builder.getEntry(INSTANCE).setDouble(m_instance);
IntegerPublisher instancePub = new IntegerTopic(builder.getTopic(INSTANCE)).publish();
instancePub.set(m_instance);
builder.addCloseable(instancePub);
builder.addStringProperty(DEFAULT, () -> m_defaultChoice, null);
builder.addStringArrayProperty(OPTIONS, () -> m_map.keySet().toArray(new String[0]), null);
builder.addStringProperty(
@@ -130,7 +143,7 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
null);
m_mutex.lock();
try {
m_activeEntries.add(builder.getEntry(ACTIVE));
m_activePubs.add(new StringTopic(builder.getTopic(ACTIVE)).publish());
} finally {
m_mutex.unlock();
}
@@ -141,8 +154,8 @@ public class SendableChooser<V> implements NTSendable, AutoCloseable {
m_mutex.lock();
try {
m_selected = val;
for (NetworkTableEntry entry : m_activeEntries) {
entry.setString(val);
for (StringPublisher pub : m_activePubs) {
pub.set(val);
}
} finally {
m_mutex.unlock();

View File

@@ -11,7 +11,6 @@ import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.util.sendable.Sendable;
import edu.wpi.first.util.sendable.SendableRegistry;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -25,8 +24,7 @@ import java.util.Set;
*/
public final class SmartDashboard {
/** The {@link NetworkTable} used by {@link SmartDashboard}. */
private static final NetworkTable table =
NetworkTableInstance.getDefault().getTable("SmartDashboard");
private static NetworkTable table;
/**
* A table linking tables in the SmartDashboard to the {@link Sendable} objects they came from.
@@ -38,6 +36,7 @@ public final class SmartDashboard {
private static final ListenerExecutor listenerExecutor = new ListenerExecutor();
static {
setNetworkTableInstance(NetworkTableInstance.getDefault());
HAL.report(tResourceType.kResourceType_SmartDashboard, 0);
}
@@ -45,6 +44,16 @@ public final class SmartDashboard {
throw new UnsupportedOperationException("This is a utility class!");
}
/**
* Set the NetworkTable instance used for entries. For testing purposes; use with caution.
*
* @param inst NetworkTable instance
*/
public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
SmartDashboard.table = inst.getTable("SmartDashboard");
tablesToData.clear();
}
/**
* Maps the specified key to the specified value in this table. The key can not be null. The value
* can be retrieved by calling the get method with a key that is equal to the original key.
@@ -165,45 +174,6 @@ public final class SmartDashboard {
return getEntry(key).isPersistent();
}
/**
* Sets flags on the specified key in this table. The key can not be null.
*
* @param key the key name
* @param flags the flags to set (bitmask)
*/
public static void setFlags(String key, int flags) {
getEntry(key).setFlags(flags);
}
/**
* Clears flags on the specified key in this table. The key can not be null.
*
* @param key the key name
* @param flags the flags to clear (bitmask)
*/
public static void clearFlags(String key, int flags) {
getEntry(key).clearFlags(flags);
}
/**
* Returns the flags for the specified key.
*
* @param key the key name
* @return the flags, or 0 if the key is not defined
*/
public static int getFlags(String key) {
return getEntry(key).getFlags();
}
/**
* Deletes the specified key in this table. The key can not be null.
*
* @param key the key name
*/
public static void delete(String key) {
table.delete(key);
}
/**
* Put a boolean in the table.
*
@@ -495,18 +465,6 @@ public final class SmartDashboard {
return getEntry(key).setRaw(value);
}
/**
* Put a raw value (bytes from a byte buffer) in the table.
*
* @param key the key to be assigned to
* @param value the value that will be assigned
* @param len the length of the value
* @return False if the table key already exists with a different type
*/
public static boolean putRaw(String key, ByteBuffer value, int len) {
return getEntry(key).setRaw(value, len);
}
/**
* Gets the current value in the table, setting it if it does not exist.
*