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