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