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