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