1+ // Copyright (c) 2025-2026 Littleton Robotics
2+ // http://github.com/Mechanical-Advantage
3+ //
4+ // Use of this source code is governed by an MIT-style
5+ // license that can be found in the LICENSE file at
6+ // the root directory of this project.
7+
8+ package frc .robot .util ;
9+
10+ import java .util .Arrays ;
11+ import java .util .HashMap ;
12+ import java .util .Map ;
13+ import java .util .function .Consumer ;
14+ import java .util .function .DoubleSupplier ;
15+ import frc .robot .Constants ;
16+ import org .littletonrobotics .junction .networktables .LoggedNetworkNumber ;
17+
18+ /**
19+ * Class for a tunable number. Gets value from dashboard in tuning mode, returns default if not or
20+ * value not in dashboard.
21+ */
22+ @ SuppressWarnings ("unused" )
23+ public class LoggedTunableNumber implements DoubleSupplier {
24+ private static final String tableKey = "/Tuning" ;
25+
26+ private final String key ;
27+ private boolean hasDefault = false ;
28+ private double defaultValue ;
29+ private LoggedNetworkNumber dashboardNumber ;
30+ private Map <Integer , Double > lastHasChangedValues = new HashMap <>();
31+
32+ /**
33+ * Create a new LoggedTunableNumber
34+ *
35+ * @param dashboardKey Key on dashboard
36+ */
37+ public LoggedTunableNumber (String dashboardKey ) {
38+ this .key = tableKey + "/" + dashboardKey ;
39+ }
40+
41+ /**
42+ * Create a new LoggedTunableNumber with the default value
43+ *
44+ * @param dashboardKey Key on dashboard
45+ * @param defaultValue Default value
46+ */
47+ public LoggedTunableNumber (String dashboardKey , double defaultValue ) {
48+ this (dashboardKey );
49+ initDefault (defaultValue );
50+ }
51+
52+ /**
53+ * Set the default value of the number. The default value can only be set once.
54+ *
55+ * @param defaultValue The default value
56+ */
57+ public void initDefault (double defaultValue ) {
58+ if (!hasDefault ) {
59+ hasDefault = true ;
60+ this .defaultValue = defaultValue ;
61+ if (Constants .tuningMode ) {
62+ dashboardNumber = new LoggedNetworkNumber (key , defaultValue );
63+ }
64+ }
65+ }
66+
67+ /**
68+ * Get the current value, from dashboard if available and in tuning mode.
69+ *
70+ * @return The current value
71+ */
72+ public double get () {
73+ if (!hasDefault ) {
74+ return 0.0 ;
75+ } else {
76+ return Constants .tuningMode ? dashboardNumber .get () : defaultValue ;
77+ }
78+ }
79+
80+ /**
81+ * Checks whether the number has changed since our last check
82+ *
83+ * @param id Unique identifier for the caller to avoid conflicts when shared between multiple
84+ * objects. Recommended approach is to pass the result of "hashCode()"
85+ * @return True if the number has changed since the last time this method was called, false
86+ * otherwise.
87+ */
88+ public boolean hasChanged (int id ) {
89+ double currentValue = get ();
90+ Double lastValue = lastHasChangedValues .get (id );
91+ if (lastValue == null || currentValue != lastValue ) {
92+ lastHasChangedValues .put (id , currentValue );
93+ return true ;
94+ }
95+
96+ return false ;
97+ }
98+
99+ /**
100+ * Runs action if any of the tunableNumbers have changed
101+ *
102+ * @param id Unique identifier for the caller to avoid conflicts when shared between multiple *
103+ * objects. Recommended approach is to pass the result of "hashCode()"
104+ * @param action Callback to run when any of the tunable numbers have changed. Access tunable
105+ * numbers in order inputted in method
106+ * @param tunableNumbers All tunable numbers to check
107+ */
108+ public static void ifChanged (
109+ int id , Consumer <double []> action , LoggedTunableNumber ... tunableNumbers ) {
110+ if (Arrays .stream (tunableNumbers ).anyMatch (tunableNumber -> tunableNumber .hasChanged (id ))) {
111+ action .accept (Arrays .stream (tunableNumbers ).mapToDouble (LoggedTunableNumber ::get ).toArray ());
112+ }
113+ }
114+
115+ /** Runs action if any of the tunableNumbers have changed */
116+ public static void ifChanged (int id , Runnable action , LoggedTunableNumber ... tunableNumbers ) {
117+ ifChanged (id , values -> action .run (), tunableNumbers );
118+ }
119+
120+ @ Override
121+ public double getAsDouble () {
122+ return get ();
123+ }
124+ }
0 commit comments