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