Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@

package org.littletonrobotics.junction.networktables;

import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.littletonrobotics.junction.LogTable;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.inputs.LoggableInputs;

import edu.wpi.first.wpilibj.smartdashboard.SendableChooser;
import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard;

/**
* Manages a chooser value published to the "SmartDashboard" table of NT.
*/
public class LoggedDashboardChooser<V> extends LoggedNetworkInput {

private final String key;
private String selectedValue = null;
private SendableChooser<String> sendableChooser = new SendableChooser<>();
Expand Down Expand Up @@ -70,30 +69,34 @@ public LoggedDashboardChooser(String key) {
@SuppressWarnings("unchecked")
public LoggedDashboardChooser(String key, SendableChooser<V> chooser) {
this(key);

// Get options map
Map<String, V> options = new HashMap<>();
try {
Field mapField = SendableChooser.class.getDeclaredField("m_map");
mapField.setAccessible(true);
options = (Map<String, V>) mapField.get(chooser);
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {
} catch (
NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e
) {
e.printStackTrace();
}

// Get default option
String defaultString = "";
try {
Field defaultField = SendableChooser.class.getDeclaredField("m_defaultChoice");
Field defaultField =
SendableChooser.class.getDeclaredField("m_defaultChoice");
defaultField.setAccessible(true);
defaultString = (String) defaultField.get(chooser);
} catch (NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e) {
} catch (
NoSuchFieldException
| SecurityException
| IllegalArgumentException
| IllegalAccessException e
) {
e.printStackTrace();
}

Expand Down Expand Up @@ -142,4 +145,4 @@ public void periodic() {
}
Logger.processInputs(prefix + "/SmartDashboard", inputs);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@

import edu.wpi.first.networktables.BooleanEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.wpilibj.DriverStation;
import org.littletonrobotics.junction.LogTable;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.inputs.LoggableInputs;

/** Manages a boolean value published to the root table of NT. */
public class LoggedNetworkBoolean extends LoggedNetworkInput {
public class LoggedNetworkBoolean
extends ObservableLoggedNetworkInput<Boolean> {

private static final boolean DEFAULT_VALUE = false;

private final String key;
private final BooleanEntry entry;
private boolean defaultValue = false;
private boolean defaultValue = DEFAULT_VALUE;
private boolean value;

/**
Expand All @@ -34,8 +40,15 @@ public class LoggedNetworkBoolean extends LoggedNetworkInput {
* "/DashboardInputs/{key}" when logged.
*/
public LoggedNetworkBoolean(String key) {
super(DEFAULT_VALUE);
this.key = key;
this.entry = NetworkTableInstance.getDefault().getBooleanTopic(key).getEntry(false);
this.entry = NetworkTableInstance.getDefault()
.getBooleanTopic(key)
.getEntry(
DEFAULT_VALUE,
PubSubOption.keepDuplicates(false),
PubSubOption.pollStorage(1)
);
this.value = defaultValue;
Logger.registerDashboardInput(this);
}
Expand Down Expand Up @@ -69,7 +82,7 @@ public void set(boolean value) {
}

/** Returns the current value. */
public boolean get() {
public Boolean get() {
return value;
}

Expand All @@ -86,6 +99,14 @@ public void fromLog(LogTable table) {
public void periodic() {
if (!Logger.hasReplaySource()) {
value = entry.get(defaultValue);

// Only do tunables when not in a match
if (DriverStation.getMatchType() == DriverStation.MatchType.None) {
var changes = entry.readQueueValues();
if (changes.length == 1) {
notifyListeners();
}
}
}
Logger.processInputs(prefix, inputs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@

import edu.wpi.first.networktables.DoubleEntry;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.wpilibj.DriverStation;
import org.littletonrobotics.junction.LogTable;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.inputs.LoggableInputs;

/** Manages a number value published to the root table of NT. */
public class LoggedNetworkNumber extends LoggedNetworkInput {
public class LoggedNetworkNumber extends ObservableLoggedNetworkInput<Double> {

private static final double DEFAULT_VALUE = 0.0;

private final String key;
private final DoubleEntry entry;
private double defaultValue = 0.0;
private double defaultValue = DEFAULT_VALUE;
private double value;

/**
Expand All @@ -34,8 +39,15 @@ public class LoggedNetworkNumber extends LoggedNetworkInput {
* "/DashboardInputs/{key}" when logged.
*/
public LoggedNetworkNumber(String key) {
super(DEFAULT_VALUE);
this.key = key;
this.entry = NetworkTableInstance.getDefault().getDoubleTopic(key).getEntry(0.0);
this.entry = NetworkTableInstance.getDefault()
.getDoubleTopic(key)
.getEntry(
DEFAULT_VALUE,
PubSubOption.keepDuplicates(false),
PubSubOption.pollStorage(1)
);
this.value = defaultValue;
Logger.registerDashboardInput(this);
}
Expand Down Expand Up @@ -69,7 +81,7 @@ public void set(double value) {
}

/** Returns the current value. */
public double get() {
public Double get() {
return value;
}

Expand All @@ -86,6 +98,14 @@ public void fromLog(LogTable table) {
public void periodic() {
if (!Logger.hasReplaySource()) {
value = entry.get(defaultValue);

// Only do tunables when not in a match
if (DriverStation.getMatchType() == DriverStation.MatchType.None) {
var changes = entry.readQueueValues();
if (changes.length == 1) {
notifyListeners();
}
}
}
Logger.processInputs(prefix, inputs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@
package org.littletonrobotics.junction.networktables;

import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.networktables.PubSubOption;
import edu.wpi.first.networktables.StringEntry;
import edu.wpi.first.wpilibj.DriverStation;
import org.littletonrobotics.junction.LogTable;
import org.littletonrobotics.junction.Logger;
import org.littletonrobotics.junction.inputs.LoggableInputs;

/** Manages a String value published to the root table of NT. */
public class LoggedNetworkString extends LoggedNetworkInput {
public class LoggedNetworkString extends ObservableLoggedNetworkInput<String> {

private static final String DEFAULT_VALUE = "";

private final String key;
private final StringEntry entry;
private String defaultValue = "";
private String defaultValue = DEFAULT_VALUE;
private String value;

/**
Expand All @@ -34,8 +39,15 @@ public class LoggedNetworkString extends LoggedNetworkInput {
* "/DashboardInputs/{key}" when logged.
*/
public LoggedNetworkString(String key) {
super(DEFAULT_VALUE);
this.key = key;
this.entry = NetworkTableInstance.getDefault().getStringTopic(key).getEntry("");
this.entry = NetworkTableInstance.getDefault()
.getStringTopic(key)
.getEntry(
DEFAULT_VALUE,
PubSubOption.keepDuplicates(false),
PubSubOption.pollStorage(1)
);
this.value = defaultValue;
Logger.registerDashboardInput(this);
}
Expand Down Expand Up @@ -86,6 +98,14 @@ public void fromLog(LogTable table) {
public void periodic() {
if (!Logger.hasReplaySource()) {
value = entry.get(defaultValue);

// Only do tunables when not in a match
if (DriverStation.getMatchType() == DriverStation.MatchType.None) {
var changes = entry.readQueueValues();
if (changes.length == 1) {
notifyListeners();
}
}
}
Logger.processInputs(prefix, inputs);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2021-2025 FRC 6328
// http://github.com/Mechanical-Advantage
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// version 3 as published by the Free Software Foundation or
// available in the root directory of this project.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

package org.littletonrobotics.junction.networktables;

import edu.wpi.first.wpilibj.DriverStation;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;

public abstract class ObservableLoggedNetworkInput<T>
extends LoggedNetworkInput {

public static final String prefix = "NetworkInputs";
private final T defaultValue;
private final List<WeakReference<Consumer<T>>> listeners;

protected ObservableLoggedNetworkInput(T defaultValue) {
this.defaultValue = defaultValue;
this.listeners = new ArrayList<>();
}

public abstract T get();

/** Removes the leading slash from a key. */
protected static String removeSlash(String key) {
if (key.startsWith("/")) {
return key.substring(1);
} else {
return key;
}
}

public void addListener(Consumer<T> listener) {
listeners.add(new WeakReference<>(listener));

// Only do tunables when not in a match, when in a match, give listener the
// default value
if (DriverStation.getMatchType() == DriverStation.MatchType.None) {
listener.accept(get());
} else {
listener.accept(defaultValue);
}
}

public void removeListener(Consumer<T> listener) {
Iterator<WeakReference<Consumer<T>>> iter = listeners.iterator();
while (iter.hasNext()) {
WeakReference<Consumer<T>> ref = iter.next();
Consumer<T> current = ref.get();
if (current == null || current == listener) {
iter.remove();
}
}
}

protected void notifyListeners() {
Iterator<WeakReference<Consumer<T>>> iter = listeners.iterator();
while (iter.hasNext()) {
WeakReference<Consumer<T>> ref = iter.next();
Consumer<T> listener = ref.get();
if (listener != null) {
listener.accept(get());
} else {
iter.remove();
}
}
}
}