001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.wpilibj.smartdashboard;
006
007import edu.wpi.first.networktables.BooleanArrayPublisher;
008import edu.wpi.first.networktables.BooleanArraySubscriber;
009import edu.wpi.first.networktables.BooleanArrayTopic;
010import edu.wpi.first.networktables.BooleanPublisher;
011import edu.wpi.first.networktables.BooleanSubscriber;
012import edu.wpi.first.networktables.BooleanTopic;
013import edu.wpi.first.networktables.DoubleArrayPublisher;
014import edu.wpi.first.networktables.DoubleArraySubscriber;
015import edu.wpi.first.networktables.DoubleArrayTopic;
016import edu.wpi.first.networktables.DoublePublisher;
017import edu.wpi.first.networktables.DoubleSubscriber;
018import edu.wpi.first.networktables.DoubleTopic;
019import edu.wpi.first.networktables.FloatArrayPublisher;
020import edu.wpi.first.networktables.FloatArraySubscriber;
021import edu.wpi.first.networktables.FloatArrayTopic;
022import edu.wpi.first.networktables.FloatPublisher;
023import edu.wpi.first.networktables.FloatSubscriber;
024import edu.wpi.first.networktables.FloatTopic;
025import edu.wpi.first.networktables.IntegerArrayPublisher;
026import edu.wpi.first.networktables.IntegerArraySubscriber;
027import edu.wpi.first.networktables.IntegerArrayTopic;
028import edu.wpi.first.networktables.IntegerPublisher;
029import edu.wpi.first.networktables.IntegerSubscriber;
030import edu.wpi.first.networktables.IntegerTopic;
031import edu.wpi.first.networktables.NTSendableBuilder;
032import edu.wpi.first.networktables.NetworkTable;
033import edu.wpi.first.networktables.PubSubOption;
034import edu.wpi.first.networktables.Publisher;
035import edu.wpi.first.networktables.RawPublisher;
036import edu.wpi.first.networktables.RawSubscriber;
037import edu.wpi.first.networktables.RawTopic;
038import edu.wpi.first.networktables.StringArrayPublisher;
039import edu.wpi.first.networktables.StringArraySubscriber;
040import edu.wpi.first.networktables.StringArrayTopic;
041import edu.wpi.first.networktables.StringPublisher;
042import edu.wpi.first.networktables.StringSubscriber;
043import edu.wpi.first.networktables.StringTopic;
044import edu.wpi.first.networktables.Subscriber;
045import edu.wpi.first.networktables.Topic;
046import edu.wpi.first.util.function.BooleanConsumer;
047import edu.wpi.first.util.function.FloatConsumer;
048import edu.wpi.first.util.function.FloatSupplier;
049import edu.wpi.first.wpilibj.RobotController;
050import java.util.ArrayList;
051import java.util.List;
052import java.util.function.BooleanSupplier;
053import java.util.function.Consumer;
054import java.util.function.DoubleConsumer;
055import java.util.function.DoubleSupplier;
056import java.util.function.LongConsumer;
057import java.util.function.LongSupplier;
058import java.util.function.Supplier;
059
060/** Implementation detail for SendableBuilder. */
061@SuppressWarnings("PMD.CompareObjectsWithEquals")
062public class SendableBuilderImpl implements NTSendableBuilder {
063  @FunctionalInterface
064  private interface TimedConsumer<T> {
065    void accept(T value, long time);
066  }
067
068  private static final class Property<P extends Publisher, S extends Subscriber>
069      implements AutoCloseable {
070    @Override
071    @SuppressWarnings("PMD.AvoidCatchingGenericException")
072    public void close() {
073      try {
074        if (m_pub != null) {
075          m_pub.close();
076        }
077        if (m_sub != null) {
078          m_sub.close();
079        }
080      } catch (Exception e) {
081        // ignore
082      }
083    }
084
085    void update(boolean controllable, long time) {
086      if (controllable && m_sub != null && m_updateLocal != null) {
087        m_updateLocal.accept(m_sub);
088      }
089      if (m_pub != null && m_updateNetwork != null) {
090        m_updateNetwork.accept(m_pub, time);
091      }
092    }
093
094    P m_pub;
095    S m_sub;
096    TimedConsumer<P> m_updateNetwork;
097    Consumer<S> m_updateLocal;
098  }
099
100  private final List<Property<?, ?>> m_properties = new ArrayList<>();
101  private Runnable m_safeState;
102  private final List<Runnable> m_updateTables = new ArrayList<>();
103  private NetworkTable m_table;
104  private boolean m_controllable;
105  private boolean m_actuator;
106
107  private BooleanPublisher m_controllablePub;
108  private StringPublisher m_typePub;
109  private BooleanPublisher m_actuatorPub;
110
111  private final List<AutoCloseable> m_closeables = new ArrayList<>();
112
113  /** Default constructor. */
114  public SendableBuilderImpl() {}
115
116  @Override
117  @SuppressWarnings("PMD.AvoidCatchingGenericException")
118  public void close() {
119    if (m_controllablePub != null) {
120      m_controllablePub.close();
121    }
122    if (m_typePub != null) {
123      m_typePub.close();
124    }
125    if (m_actuatorPub != null) {
126      m_actuatorPub.close();
127    }
128    for (Property<?, ?> property : m_properties) {
129      property.close();
130    }
131    for (AutoCloseable closeable : m_closeables) {
132      try {
133        closeable.close();
134      } catch (Exception e) {
135        // ignore
136      }
137    }
138  }
139
140  /**
141   * Set the network table. Must be called prior to any Add* functions being called.
142   *
143   * @param table Network table
144   */
145  public void setTable(NetworkTable table) {
146    m_table = table;
147    m_controllablePub = table.getBooleanTopic(".controllable").publish();
148    m_controllablePub.setDefault(false);
149  }
150
151  /**
152   * Get the network table.
153   *
154   * @return The network table
155   */
156  @Override
157  public NetworkTable getTable() {
158    return m_table;
159  }
160
161  /**
162   * Return whether this sendable has an associated table.
163   *
164   * @return True if it has a table, false if not.
165   */
166  @Override
167  public boolean isPublished() {
168    return m_table != null;
169  }
170
171  /**
172   * Return whether this sendable should be treated as an actuator.
173   *
174   * @return True if actuator, false if not.
175   */
176  public boolean isActuator() {
177    return m_actuator;
178  }
179
180  /** Update the network table values by calling the getters for all properties. */
181  @Override
182  public void update() {
183    long time = RobotController.getTime();
184    for (Property<?, ?> property : m_properties) {
185      property.update(m_controllable, time);
186    }
187    for (Runnable updateTable : m_updateTables) {
188      updateTable.run();
189    }
190  }
191
192  /** Hook setters for all properties. */
193  public void startListeners() {
194    m_controllable = true;
195    if (m_controllablePub != null) {
196      m_controllablePub.set(true);
197    }
198  }
199
200  /** Unhook setters for all properties. */
201  public void stopListeners() {
202    m_controllable = false;
203    if (m_controllablePub != null) {
204      m_controllablePub.set(false);
205    }
206  }
207
208  /**
209   * Start LiveWindow mode by hooking the setters for all properties. Also calls the safeState
210   * function if one was provided.
211   */
212  public void startLiveWindowMode() {
213    if (m_safeState != null) {
214      m_safeState.run();
215    }
216    startListeners();
217  }
218
219  /**
220   * Stop LiveWindow mode by unhooking the setters for all properties. Also calls the safeState
221   * function if one was provided.
222   */
223  public void stopLiveWindowMode() {
224    stopListeners();
225    if (m_safeState != null) {
226      m_safeState.run();
227    }
228  }
229
230  /** Clear properties. */
231  @Override
232  public void clearProperties() {
233    stopListeners();
234    for (Property<?, ?> property : m_properties) {
235      property.close();
236    }
237    m_properties.clear();
238  }
239
240  @Override
241  public void addCloseable(AutoCloseable closeable) {
242    m_closeables.add(closeable);
243  }
244
245  /**
246   * Set the string representation of the named data type that will be used by the smart dashboard
247   * for this sendable.
248   *
249   * @param type data type
250   */
251  @Override
252  public void setSmartDashboardType(String type) {
253    if (m_typePub == null) {
254      m_typePub =
255          m_table
256              .getStringTopic(".type")
257              .publishEx(StringTopic.kTypeString, "{\"SmartDashboard\":\"" + type + "\"}");
258    }
259    m_typePub.set(type);
260  }
261
262  /**
263   * Set a flag indicating if this sendable should be treated as an actuator. By default, this flag
264   * is false.
265   *
266   * @param value true if actuator, false if not
267   */
268  @Override
269  public void setActuator(boolean value) {
270    if (m_actuatorPub == null) {
271      m_actuatorPub = m_table.getBooleanTopic(".actuator").publish();
272    }
273    m_actuatorPub.set(value);
274    m_actuator = value;
275  }
276
277  /**
278   * Set the function that should be called to set the Sendable into a safe state. This is called
279   * when entering and exiting Live Window mode.
280   *
281   * @param func function
282   */
283  @Override
284  public void setSafeState(Runnable func) {
285    m_safeState = func;
286  }
287
288  /**
289   * Set the function that should be called to update the network table for things other than
290   * properties. Note this function is not passed the network table object; instead it should use
291   * the topics returned by getTopic().
292   *
293   * @param func function
294   */
295  @Override
296  public void setUpdateTable(Runnable func) {
297    m_updateTables.add(func);
298  }
299
300  /**
301   * Add a property without getters or setters. This can be used to get entry handles for the
302   * function called by setUpdateTable().
303   *
304   * @param key property name
305   * @return Network table entry
306   */
307  @Override
308  public Topic getTopic(String key) {
309    return m_table.getTopic(key);
310  }
311
312  /**
313   * Add a boolean property.
314   *
315   * @param key property name
316   * @param getter getter function (returns current value)
317   * @param setter setter function (sets new value)
318   */
319  @Override
320  public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
321    Property<BooleanPublisher, BooleanSubscriber> property = new Property<>();
322    BooleanTopic topic = m_table.getBooleanTopic(key);
323    if (getter != null) {
324      property.m_pub = topic.publish();
325      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsBoolean(), time);
326    }
327    if (setter != null) {
328      property.m_sub = topic.subscribe(false, PubSubOption.excludePublisher(property.m_pub));
329      property.m_updateLocal =
330          sub -> {
331            for (boolean val : sub.readQueueValues()) {
332              setter.accept(val);
333            }
334          };
335    }
336    m_properties.add(property);
337  }
338
339  @Override
340  public void publishConstBoolean(String key, boolean value) {
341    Property<BooleanPublisher, BooleanSubscriber> property = new Property<>();
342    BooleanTopic topic = m_table.getBooleanTopic(key);
343    property.m_pub = topic.publish();
344    property.m_pub.set(value);
345    m_properties.add(property);
346  }
347
348  /**
349   * Add an integer property.
350   *
351   * @param key property name
352   * @param getter getter function (returns current value)
353   * @param setter setter function (sets new value)
354   */
355  @Override
356  public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
357    Property<IntegerPublisher, IntegerSubscriber> property = new Property<>();
358    IntegerTopic topic = m_table.getIntegerTopic(key);
359    if (getter != null) {
360      property.m_pub = topic.publish();
361      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsLong(), time);
362    }
363    if (setter != null) {
364      property.m_sub = topic.subscribe(0, PubSubOption.excludePublisher(property.m_pub));
365      property.m_updateLocal =
366          sub -> {
367            for (long val : sub.readQueueValues()) {
368              setter.accept(val);
369            }
370          };
371    }
372    m_properties.add(property);
373  }
374
375  @Override
376  public void publishConstInteger(String key, long value) {
377    Property<IntegerPublisher, IntegerSubscriber> property = new Property<>();
378    IntegerTopic topic = m_table.getIntegerTopic(key);
379    property.m_pub = topic.publish();
380    property.m_pub.set(value);
381    m_properties.add(property);
382  }
383
384  /**
385   * Add a float property.
386   *
387   * @param key property name
388   * @param getter getter function (returns current value)
389   * @param setter setter function (sets new value)
390   */
391  @Override
392  public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
393    Property<FloatPublisher, FloatSubscriber> property = new Property<>();
394    FloatTopic topic = m_table.getFloatTopic(key);
395    if (getter != null) {
396      property.m_pub = topic.publish();
397      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsFloat(), time);
398    }
399    if (setter != null) {
400      property.m_sub = topic.subscribe(0.0f, PubSubOption.excludePublisher(property.m_pub));
401      property.m_updateLocal =
402          sub -> {
403            for (float val : sub.readQueueValues()) {
404              setter.accept(val);
405            }
406          };
407    }
408    m_properties.add(property);
409  }
410
411  @Override
412  public void publishConstFloat(String key, float value) {
413    Property<FloatPublisher, FloatSubscriber> property = new Property<>();
414    FloatTopic topic = m_table.getFloatTopic(key);
415    property.m_pub = topic.publish();
416    property.m_pub.set(value);
417    m_properties.add(property);
418  }
419
420  /**
421   * Add a double property.
422   *
423   * @param key property name
424   * @param getter getter function (returns current value)
425   * @param setter setter function (sets new value)
426   */
427  @Override
428  public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
429    Property<DoublePublisher, DoubleSubscriber> property = new Property<>();
430    DoubleTopic topic = m_table.getDoubleTopic(key);
431    if (getter != null) {
432      property.m_pub = topic.publish();
433      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsDouble(), time);
434    }
435    if (setter != null) {
436      property.m_sub = topic.subscribe(0.0, PubSubOption.excludePublisher(property.m_pub));
437      property.m_updateLocal =
438          sub -> {
439            for (double val : sub.readQueueValues()) {
440              setter.accept(val);
441            }
442          };
443    }
444    m_properties.add(property);
445  }
446
447  @Override
448  public void publishConstDouble(String key, double value) {
449    Property<DoublePublisher, DoubleSubscriber> property = new Property<>();
450    DoubleTopic topic = m_table.getDoubleTopic(key);
451    property.m_pub = topic.publish();
452    property.m_pub.set(value);
453    m_properties.add(property);
454  }
455
456  /**
457   * Add a string property.
458   *
459   * @param key property name
460   * @param getter getter function (returns current value)
461   * @param setter setter function (sets new value)
462   */
463  @Override
464  public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
465    Property<StringPublisher, StringSubscriber> property = new Property<>();
466    StringTopic topic = m_table.getStringTopic(key);
467    if (getter != null) {
468      property.m_pub = topic.publish();
469      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
470    }
471    if (setter != null) {
472      property.m_sub = topic.subscribe("", PubSubOption.excludePublisher(property.m_pub));
473      property.m_updateLocal =
474          sub -> {
475            for (String val : sub.readQueueValues()) {
476              setter.accept(val);
477            }
478          };
479    }
480    m_properties.add(property);
481  }
482
483  @Override
484  public void publishConstString(String key, String value) {
485    Property<StringPublisher, StringSubscriber> property = new Property<>();
486    StringTopic topic = m_table.getStringTopic(key);
487    property.m_pub = topic.publish();
488    property.m_pub.set(value);
489    m_properties.add(property);
490  }
491
492  /**
493   * Add a boolean array property.
494   *
495   * @param key property name
496   * @param getter getter function (returns current value)
497   * @param setter setter function (sets new value)
498   */
499  @Override
500  public void addBooleanArrayProperty(
501      String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
502    Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>();
503    BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key);
504    if (getter != null) {
505      property.m_pub = topic.publish();
506      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
507    }
508    if (setter != null) {
509      property.m_sub =
510          topic.subscribe(new boolean[] {}, PubSubOption.excludePublisher(property.m_pub));
511      property.m_updateLocal =
512          sub -> {
513            for (boolean[] val : sub.readQueueValues()) {
514              setter.accept(val);
515            }
516          };
517    }
518    m_properties.add(property);
519  }
520
521  @Override
522  public void publishConstBooleanArray(String key, boolean[] value) {
523    Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>();
524    BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key);
525    property.m_pub = topic.publish();
526    property.m_pub.set(value);
527    m_properties.add(property);
528  }
529
530  /**
531   * Add an integer array property.
532   *
533   * @param key property name
534   * @param getter getter function (returns current value)
535   * @param setter setter function (sets new value)
536   */
537  @Override
538  public void addIntegerArrayProperty(
539      String key, Supplier<long[]> getter, Consumer<long[]> setter) {
540    Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>();
541    IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key);
542    if (getter != null) {
543      property.m_pub = topic.publish();
544      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
545    }
546    if (setter != null) {
547      property.m_sub =
548          topic.subscribe(new long[] {}, PubSubOption.excludePublisher(property.m_pub));
549      property.m_updateLocal =
550          sub -> {
551            for (long[] val : sub.readQueueValues()) {
552              setter.accept(val);
553            }
554          };
555    }
556    m_properties.add(property);
557  }
558
559  @Override
560  public void publishConstIntegerArray(String key, long[] value) {
561    Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>();
562    IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key);
563    property.m_pub = topic.publish();
564    property.m_pub.set(value);
565    m_properties.add(property);
566  }
567
568  /**
569   * Add a float array property.
570   *
571   * @param key property name
572   * @param getter getter function (returns current value)
573   * @param setter setter function (sets new value)
574   */
575  @Override
576  public void addFloatArrayProperty(
577      String key, Supplier<float[]> getter, Consumer<float[]> setter) {
578    Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>();
579    FloatArrayTopic topic = m_table.getFloatArrayTopic(key);
580    if (getter != null) {
581      property.m_pub = topic.publish();
582      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
583    }
584    if (setter != null) {
585      property.m_sub =
586          topic.subscribe(new float[] {}, PubSubOption.excludePublisher(property.m_pub));
587      property.m_updateLocal =
588          sub -> {
589            for (float[] val : sub.readQueueValues()) {
590              setter.accept(val);
591            }
592          };
593    }
594    m_properties.add(property);
595  }
596
597  @Override
598  public void publishConstFloatArray(String key, float[] value) {
599    Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>();
600    FloatArrayTopic topic = m_table.getFloatArrayTopic(key);
601    property.m_pub = topic.publish();
602    property.m_pub.set(value);
603    m_properties.add(property);
604  }
605
606  /**
607   * Add a double array property.
608   *
609   * @param key property name
610   * @param getter getter function (returns current value)
611   * @param setter setter function (sets new value)
612   */
613  @Override
614  public void addDoubleArrayProperty(
615      String key, Supplier<double[]> getter, Consumer<double[]> setter) {
616    Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>();
617    DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key);
618    if (getter != null) {
619      property.m_pub = topic.publish();
620      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
621    }
622    if (setter != null) {
623      property.m_sub =
624          topic.subscribe(new double[] {}, PubSubOption.excludePublisher(property.m_pub));
625      property.m_updateLocal =
626          sub -> {
627            for (double[] val : sub.readQueueValues()) {
628              setter.accept(val);
629            }
630          };
631    }
632    m_properties.add(property);
633  }
634
635  @Override
636  public void publishConstDoubleArray(String key, double[] value) {
637    Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>();
638    DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key);
639    property.m_pub = topic.publish();
640    property.m_pub.set(value);
641    m_properties.add(property);
642  }
643
644  /**
645   * Add a string array property.
646   *
647   * @param key property name
648   * @param getter getter function (returns current value)
649   * @param setter setter function (sets new value)
650   */
651  @Override
652  public void addStringArrayProperty(
653      String key, Supplier<String[]> getter, Consumer<String[]> setter) {
654    Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>();
655    StringArrayTopic topic = m_table.getStringArrayTopic(key);
656    if (getter != null) {
657      property.m_pub = topic.publish();
658      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
659    }
660    if (setter != null) {
661      property.m_sub =
662          topic.subscribe(new String[] {}, PubSubOption.excludePublisher(property.m_pub));
663      property.m_updateLocal =
664          sub -> {
665            for (String[] val : sub.readQueueValues()) {
666              setter.accept(val);
667            }
668          };
669    }
670    m_properties.add(property);
671  }
672
673  @Override
674  public void publishConstStringArray(String key, String[] value) {
675    Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>();
676    StringArrayTopic topic = m_table.getStringArrayTopic(key);
677    property.m_pub = topic.publish();
678    property.m_pub.set(value);
679    m_properties.add(property);
680  }
681
682  /**
683   * Add a raw property.
684   *
685   * @param key property name
686   * @param typeString type string
687   * @param getter getter function (returns current value)
688   * @param setter setter function (sets new value)
689   */
690  @Override
691  public void addRawProperty(
692      String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
693    Property<RawPublisher, RawSubscriber> property = new Property<>();
694    RawTopic topic = m_table.getRawTopic(key);
695    if (getter != null) {
696      property.m_pub = topic.publish(typeString);
697      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
698    }
699    if (setter != null) {
700      property.m_sub =
701          topic.subscribe(typeString, new byte[] {}, PubSubOption.excludePublisher(property.m_pub));
702      property.m_updateLocal =
703          sub -> {
704            for (byte[] val : sub.readQueueValues()) {
705              setter.accept(val);
706            }
707          };
708    }
709    m_properties.add(property);
710  }
711
712  @Override
713  public void publishConstRaw(String key, String typestring, byte[] value) {
714    Property<RawPublisher, RawSubscriber> property = new Property<>();
715    RawTopic topic = m_table.getRawTopic(key);
716    property.m_pub = topic.publish(typestring);
717    property.m_pub.set(value);
718    m_properties.add(property);
719  }
720}