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 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  /** Clear properties. */
208  @Override
209  public void clearProperties() {
210    stopListeners();
211    for (Property<?, ?> property : m_properties) {
212      property.close();
213    }
214    m_properties.clear();
215  }
216
217  @Override
218  public void addCloseable(AutoCloseable closeable) {
219    m_closeables.add(closeable);
220  }
221
222  /**
223   * Set the string representation of the named data type that will be used by the smart dashboard
224   * for this sendable.
225   *
226   * @param type data type
227   */
228  @Override
229  public void setSmartDashboardType(String type) {
230    if (m_typePub == null) {
231      m_typePub =
232          m_table
233              .getStringTopic(".type")
234              .publishEx(StringTopic.kTypeString, "{\"SmartDashboard\":\"" + type + "\"}");
235    }
236    m_typePub.set(type);
237  }
238
239  /**
240   * Set a flag indicating if this sendable should be treated as an actuator. By default, this flag
241   * is false.
242   *
243   * @param value true if actuator, false if not
244   */
245  @Override
246  public void setActuator(boolean value) {
247    if (m_actuatorPub == null) {
248      m_actuatorPub = m_table.getBooleanTopic(".actuator").publish();
249    }
250    m_actuatorPub.set(value);
251    m_actuator = value;
252  }
253
254  /**
255   * Set the function that should be called to update the network table for things other than
256   * properties. Note this function is not passed the network table object; instead it should use
257   * the topics returned by getTopic().
258   *
259   * @param func function
260   */
261  @Override
262  public void setUpdateTable(Runnable func) {
263    m_updateTables.add(func);
264  }
265
266  /**
267   * Add a property without getters or setters. This can be used to get entry handles for the
268   * function called by setUpdateTable().
269   *
270   * @param key property name
271   * @return Network table entry
272   */
273  @Override
274  public Topic getTopic(String key) {
275    return m_table.getTopic(key);
276  }
277
278  /**
279   * Add a boolean property.
280   *
281   * @param key property name
282   * @param getter getter function (returns current value)
283   * @param setter setter function (sets new value)
284   */
285  @Override
286  public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
287    Property<BooleanPublisher, BooleanSubscriber> property = new Property<>();
288    BooleanTopic topic = m_table.getBooleanTopic(key);
289    if (getter != null) {
290      property.m_pub = topic.publish();
291      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsBoolean(), time);
292    }
293    if (setter != null) {
294      property.m_sub = topic.subscribe(false, PubSubOption.excludePublisher(property.m_pub));
295      property.m_updateLocal =
296          sub -> {
297            for (boolean val : sub.readQueueValues()) {
298              setter.accept(val);
299            }
300          };
301    }
302    m_properties.add(property);
303  }
304
305  @Override
306  public void publishConstBoolean(String key, boolean value) {
307    Property<BooleanPublisher, BooleanSubscriber> property = new Property<>();
308    BooleanTopic topic = m_table.getBooleanTopic(key);
309    property.m_pub = topic.publish();
310    property.m_pub.set(value);
311    m_properties.add(property);
312  }
313
314  /**
315   * Add an integer property.
316   *
317   * @param key property name
318   * @param getter getter function (returns current value)
319   * @param setter setter function (sets new value)
320   */
321  @Override
322  public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
323    Property<IntegerPublisher, IntegerSubscriber> property = new Property<>();
324    IntegerTopic topic = m_table.getIntegerTopic(key);
325    if (getter != null) {
326      property.m_pub = topic.publish();
327      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsLong(), time);
328    }
329    if (setter != null) {
330      property.m_sub = topic.subscribe(0, PubSubOption.excludePublisher(property.m_pub));
331      property.m_updateLocal =
332          sub -> {
333            for (long val : sub.readQueueValues()) {
334              setter.accept(val);
335            }
336          };
337    }
338    m_properties.add(property);
339  }
340
341  @Override
342  public void publishConstInteger(String key, long value) {
343    Property<IntegerPublisher, IntegerSubscriber> property = new Property<>();
344    IntegerTopic topic = m_table.getIntegerTopic(key);
345    property.m_pub = topic.publish();
346    property.m_pub.set(value);
347    m_properties.add(property);
348  }
349
350  /**
351   * Add a float property.
352   *
353   * @param key property name
354   * @param getter getter function (returns current value)
355   * @param setter setter function (sets new value)
356   */
357  @Override
358  public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
359    Property<FloatPublisher, FloatSubscriber> property = new Property<>();
360    FloatTopic topic = m_table.getFloatTopic(key);
361    if (getter != null) {
362      property.m_pub = topic.publish();
363      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsFloat(), time);
364    }
365    if (setter != null) {
366      property.m_sub = topic.subscribe(0.0f, PubSubOption.excludePublisher(property.m_pub));
367      property.m_updateLocal =
368          sub -> {
369            for (float val : sub.readQueueValues()) {
370              setter.accept(val);
371            }
372          };
373    }
374    m_properties.add(property);
375  }
376
377  @Override
378  public void publishConstFloat(String key, float value) {
379    Property<FloatPublisher, FloatSubscriber> property = new Property<>();
380    FloatTopic topic = m_table.getFloatTopic(key);
381    property.m_pub = topic.publish();
382    property.m_pub.set(value);
383    m_properties.add(property);
384  }
385
386  /**
387   * Add a double property.
388   *
389   * @param key property name
390   * @param getter getter function (returns current value)
391   * @param setter setter function (sets new value)
392   */
393  @Override
394  public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
395    Property<DoublePublisher, DoubleSubscriber> property = new Property<>();
396    DoubleTopic topic = m_table.getDoubleTopic(key);
397    if (getter != null) {
398      property.m_pub = topic.publish();
399      property.m_updateNetwork = (pub, time) -> pub.set(getter.getAsDouble(), time);
400    }
401    if (setter != null) {
402      property.m_sub = topic.subscribe(0.0, PubSubOption.excludePublisher(property.m_pub));
403      property.m_updateLocal =
404          sub -> {
405            for (double val : sub.readQueueValues()) {
406              setter.accept(val);
407            }
408          };
409    }
410    m_properties.add(property);
411  }
412
413  @Override
414  public void publishConstDouble(String key, double value) {
415    Property<DoublePublisher, DoubleSubscriber> property = new Property<>();
416    DoubleTopic topic = m_table.getDoubleTopic(key);
417    property.m_pub = topic.publish();
418    property.m_pub.set(value);
419    m_properties.add(property);
420  }
421
422  /**
423   * Add a string property.
424   *
425   * @param key property name
426   * @param getter getter function (returns current value)
427   * @param setter setter function (sets new value)
428   */
429  @Override
430  public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
431    Property<StringPublisher, StringSubscriber> property = new Property<>();
432    StringTopic topic = m_table.getStringTopic(key);
433    if (getter != null) {
434      property.m_pub = topic.publish();
435      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
436    }
437    if (setter != null) {
438      property.m_sub = topic.subscribe("", PubSubOption.excludePublisher(property.m_pub));
439      property.m_updateLocal =
440          sub -> {
441            for (String val : sub.readQueueValues()) {
442              setter.accept(val);
443            }
444          };
445    }
446    m_properties.add(property);
447  }
448
449  @Override
450  public void publishConstString(String key, String value) {
451    Property<StringPublisher, StringSubscriber> property = new Property<>();
452    StringTopic topic = m_table.getStringTopic(key);
453    property.m_pub = topic.publish();
454    property.m_pub.set(value);
455    m_properties.add(property);
456  }
457
458  /**
459   * Add a boolean array property.
460   *
461   * @param key property name
462   * @param getter getter function (returns current value)
463   * @param setter setter function (sets new value)
464   */
465  @Override
466  public void addBooleanArrayProperty(
467      String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
468    Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>();
469    BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key);
470    if (getter != null) {
471      property.m_pub = topic.publish();
472      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
473    }
474    if (setter != null) {
475      property.m_sub =
476          topic.subscribe(new boolean[] {}, PubSubOption.excludePublisher(property.m_pub));
477      property.m_updateLocal =
478          sub -> {
479            for (boolean[] val : sub.readQueueValues()) {
480              setter.accept(val);
481            }
482          };
483    }
484    m_properties.add(property);
485  }
486
487  @Override
488  public void publishConstBooleanArray(String key, boolean[] value) {
489    Property<BooleanArrayPublisher, BooleanArraySubscriber> property = new Property<>();
490    BooleanArrayTopic topic = m_table.getBooleanArrayTopic(key);
491    property.m_pub = topic.publish();
492    property.m_pub.set(value);
493    m_properties.add(property);
494  }
495
496  /**
497   * Add an integer array property.
498   *
499   * @param key property name
500   * @param getter getter function (returns current value)
501   * @param setter setter function (sets new value)
502   */
503  @Override
504  public void addIntegerArrayProperty(
505      String key, Supplier<long[]> getter, Consumer<long[]> setter) {
506    Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>();
507    IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key);
508    if (getter != null) {
509      property.m_pub = topic.publish();
510      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
511    }
512    if (setter != null) {
513      property.m_sub =
514          topic.subscribe(new long[] {}, PubSubOption.excludePublisher(property.m_pub));
515      property.m_updateLocal =
516          sub -> {
517            for (long[] val : sub.readQueueValues()) {
518              setter.accept(val);
519            }
520          };
521    }
522    m_properties.add(property);
523  }
524
525  @Override
526  public void publishConstIntegerArray(String key, long[] value) {
527    Property<IntegerArrayPublisher, IntegerArraySubscriber> property = new Property<>();
528    IntegerArrayTopic topic = m_table.getIntegerArrayTopic(key);
529    property.m_pub = topic.publish();
530    property.m_pub.set(value);
531    m_properties.add(property);
532  }
533
534  /**
535   * Add a float array property.
536   *
537   * @param key property name
538   * @param getter getter function (returns current value)
539   * @param setter setter function (sets new value)
540   */
541  @Override
542  public void addFloatArrayProperty(
543      String key, Supplier<float[]> getter, Consumer<float[]> setter) {
544    Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>();
545    FloatArrayTopic topic = m_table.getFloatArrayTopic(key);
546    if (getter != null) {
547      property.m_pub = topic.publish();
548      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
549    }
550    if (setter != null) {
551      property.m_sub =
552          topic.subscribe(new float[] {}, PubSubOption.excludePublisher(property.m_pub));
553      property.m_updateLocal =
554          sub -> {
555            for (float[] val : sub.readQueueValues()) {
556              setter.accept(val);
557            }
558          };
559    }
560    m_properties.add(property);
561  }
562
563  @Override
564  public void publishConstFloatArray(String key, float[] value) {
565    Property<FloatArrayPublisher, FloatArraySubscriber> property = new Property<>();
566    FloatArrayTopic topic = m_table.getFloatArrayTopic(key);
567    property.m_pub = topic.publish();
568    property.m_pub.set(value);
569    m_properties.add(property);
570  }
571
572  /**
573   * Add a double array property.
574   *
575   * @param key property name
576   * @param getter getter function (returns current value)
577   * @param setter setter function (sets new value)
578   */
579  @Override
580  public void addDoubleArrayProperty(
581      String key, Supplier<double[]> getter, Consumer<double[]> setter) {
582    Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>();
583    DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key);
584    if (getter != null) {
585      property.m_pub = topic.publish();
586      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
587    }
588    if (setter != null) {
589      property.m_sub =
590          topic.subscribe(new double[] {}, PubSubOption.excludePublisher(property.m_pub));
591      property.m_updateLocal =
592          sub -> {
593            for (double[] val : sub.readQueueValues()) {
594              setter.accept(val);
595            }
596          };
597    }
598    m_properties.add(property);
599  }
600
601  @Override
602  public void publishConstDoubleArray(String key, double[] value) {
603    Property<DoubleArrayPublisher, DoubleArraySubscriber> property = new Property<>();
604    DoubleArrayTopic topic = m_table.getDoubleArrayTopic(key);
605    property.m_pub = topic.publish();
606    property.m_pub.set(value);
607    m_properties.add(property);
608  }
609
610  /**
611   * Add a string array property.
612   *
613   * @param key property name
614   * @param getter getter function (returns current value)
615   * @param setter setter function (sets new value)
616   */
617  @Override
618  public void addStringArrayProperty(
619      String key, Supplier<String[]> getter, Consumer<String[]> setter) {
620    Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>();
621    StringArrayTopic topic = m_table.getStringArrayTopic(key);
622    if (getter != null) {
623      property.m_pub = topic.publish();
624      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
625    }
626    if (setter != null) {
627      property.m_sub =
628          topic.subscribe(new String[] {}, PubSubOption.excludePublisher(property.m_pub));
629      property.m_updateLocal =
630          sub -> {
631            for (String[] val : sub.readQueueValues()) {
632              setter.accept(val);
633            }
634          };
635    }
636    m_properties.add(property);
637  }
638
639  @Override
640  public void publishConstStringArray(String key, String[] value) {
641    Property<StringArrayPublisher, StringArraySubscriber> property = new Property<>();
642    StringArrayTopic topic = m_table.getStringArrayTopic(key);
643    property.m_pub = topic.publish();
644    property.m_pub.set(value);
645    m_properties.add(property);
646  }
647
648  /**
649   * Add a raw property.
650   *
651   * @param key property name
652   * @param typeString type string
653   * @param getter getter function (returns current value)
654   * @param setter setter function (sets new value)
655   */
656  @Override
657  public void addRawProperty(
658      String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
659    Property<RawPublisher, RawSubscriber> property = new Property<>();
660    RawTopic topic = m_table.getRawTopic(key);
661    if (getter != null) {
662      property.m_pub = topic.publish(typeString);
663      property.m_updateNetwork = (pub, time) -> pub.set(getter.get(), time);
664    }
665    if (setter != null) {
666      property.m_sub =
667          topic.subscribe(typeString, new byte[] {}, PubSubOption.excludePublisher(property.m_pub));
668      property.m_updateLocal =
669          sub -> {
670            for (byte[] val : sub.readQueueValues()) {
671              setter.accept(val);
672            }
673          };
674    }
675    m_properties.add(property);
676  }
677
678  @Override
679  public void publishConstRaw(String key, String typestring, byte[] value) {
680    Property<RawPublisher, RawSubscriber> property = new Property<>();
681    RawTopic topic = m_table.getRawTopic(key);
682    property.m_pub = topic.publish(typestring);
683    property.m_pub.set(value);
684    m_properties.add(property);
685  }
686}