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.hal.HAL;
008import edu.wpi.first.networktables.NetworkTable;
009import edu.wpi.first.networktables.NetworkTableEntry;
010import edu.wpi.first.networktables.NetworkTableInstance;
011import edu.wpi.first.util.sendable.Sendable;
012import edu.wpi.first.util.sendable.SendableRegistry;
013import java.util.HashMap;
014import java.util.Map;
015import java.util.Set;
016
017/**
018 * The {@link SmartDashboard} class is the bridge between robot programs and the SmartDashboard on
019 * the laptop.
020 *
021 * <p>When a value is put into the SmartDashboard here, it pops up on the SmartDashboard on the
022 * laptop. Users can put values into and get values from the SmartDashboard.
023 */
024public final class SmartDashboard {
025  /** The {@link NetworkTable} used by {@link SmartDashboard}. */
026  private static NetworkTable table;
027
028  /**
029   * A table linking tables in the SmartDashboard to the {@link Sendable} objects they came from.
030   */
031  private static final Map<String, Sendable> tablesToData = new HashMap<>();
032
033  /** The executor for listener tasks; calls listener tasks synchronously from main thread. */
034  private static final ListenerExecutor listenerExecutor = new ListenerExecutor();
035
036  private static boolean m_reported = false;
037
038  static {
039    setNetworkTableInstance(NetworkTableInstance.getDefault());
040  }
041
042  private SmartDashboard() {
043    throw new UnsupportedOperationException("This is a utility class!");
044  }
045
046  /**
047   * Set the NetworkTable instance used for entries. For testing purposes; use with caution.
048   *
049   * @param inst NetworkTable instance
050   */
051  public static synchronized void setNetworkTableInstance(NetworkTableInstance inst) {
052    SmartDashboard.table = inst.getTable("SmartDashboard");
053    tablesToData.clear();
054  }
055
056  /**
057   * Maps the specified key to the specified value in this table. The key can not be null. The value
058   * can be retrieved by calling the get method with a key that is equal to the original key.
059   *
060   * @param key the key
061   * @param data the value
062   * @throws IllegalArgumentException If key is null
063   */
064  @SuppressWarnings("PMD.CompareObjectsWithEquals")
065  public static synchronized void putData(String key, Sendable data) {
066    if (!m_reported) {
067      HAL.reportUsage("SmartDashboard", "");
068      m_reported = true;
069    }
070    Sendable sddata = tablesToData.get(key);
071    if (sddata == null || sddata != data) {
072      tablesToData.put(key, data);
073      NetworkTable dataTable = table.getSubTable(key);
074      SendableBuilderImpl builder = new SendableBuilderImpl();
075      builder.setTable(dataTable);
076      SendableRegistry.publish(data, builder);
077      builder.startListeners();
078      dataTable.getEntry(".name").setString(key);
079    }
080  }
081
082  /**
083   * Maps the specified key (where the key is the name of the {@link Sendable}) to the specified
084   * value in this table. The value can be retrieved by calling the get method with a key that is
085   * equal to the original key.
086   *
087   * @param value the value
088   * @throws IllegalArgumentException If key is null
089   */
090  public static void putData(Sendable value) {
091    String name = SendableRegistry.getName(value);
092    if (!name.isEmpty()) {
093      putData(name, value);
094    }
095  }
096
097  /**
098   * Returns the value at the specified key.
099   *
100   * @param key the key
101   * @return the value
102   * @throws IllegalArgumentException if the key is null
103   */
104  public static synchronized Sendable getData(String key) {
105    Sendable data = tablesToData.get(key);
106    if (data == null) {
107      throw new IllegalArgumentException("SmartDashboard data does not exist: " + key);
108    } else {
109      return data;
110    }
111  }
112
113  /**
114   * Gets the entry for the specified key.
115   *
116   * @param key the key name
117   * @return Network table entry.
118   */
119  public static NetworkTableEntry getEntry(String key) {
120    if (!m_reported) {
121      HAL.reportUsage("SmartDashboard", "");
122      m_reported = true;
123    }
124    return table.getEntry(key);
125  }
126
127  /**
128   * Checks the table and tells if it contains the specified key.
129   *
130   * @param key the key to search for
131   * @return true if the table as a value assigned to the given key
132   */
133  public static boolean containsKey(String key) {
134    return table.containsKey(key);
135  }
136
137  /**
138   * Get the keys stored in the SmartDashboard table of NetworkTables.
139   *
140   * @param types bitmask of types; 0 is treated as a "don't care".
141   * @return keys currently in the table
142   */
143  public static Set<String> getKeys(int types) {
144    return table.getKeys(types);
145  }
146
147  /**
148   * Get the keys stored in the SmartDashboard table of NetworkTables.
149   *
150   * @return keys currently in the table.
151   */
152  public static Set<String> getKeys() {
153    return table.getKeys();
154  }
155
156  /**
157   * Makes a key's value persistent through program restarts. The key cannot be null.
158   *
159   * @param key the key name
160   */
161  public static void setPersistent(String key) {
162    getEntry(key).setPersistent();
163  }
164
165  /**
166   * Stop making a key's value persistent through program restarts. The key cannot be null.
167   *
168   * @param key the key name
169   */
170  public static void clearPersistent(String key) {
171    getEntry(key).clearPersistent();
172  }
173
174  /**
175   * Returns whether the value is persistent through program restarts. The key cannot be null.
176   *
177   * @param key the key name
178   * @return True if the value is persistent.
179   */
180  public static boolean isPersistent(String key) {
181    return getEntry(key).isPersistent();
182  }
183
184  /**
185   * Put a boolean in the table.
186   *
187   * @param key the key to be assigned to
188   * @param value the value that will be assigned
189   * @return False if the table key already exists with a different type
190   */
191  public static boolean putBoolean(String key, boolean value) {
192    return getEntry(key).setBoolean(value);
193  }
194
195  /**
196   * Set the value in the table if key does not exist.
197   *
198   * @param key the key
199   * @param defaultValue the value to set if key does not exist
200   * @return True if the key did not already exist, otherwise False
201   */
202  public static boolean setDefaultBoolean(String key, boolean defaultValue) {
203    return getEntry(key).setDefaultBoolean(defaultValue);
204  }
205
206  /**
207   * Returns the boolean the key maps to. If the key does not exist or is of different type, it will
208   * return the default value.
209   *
210   * @param key the key to look up
211   * @param defaultValue the value to be returned if no value is found
212   * @return the value associated with the given key or the given default value if there is no value
213   *     associated with the key
214   */
215  public static boolean getBoolean(String key, boolean defaultValue) {
216    return getEntry(key).getBoolean(defaultValue);
217  }
218
219  /**
220   * Put a number in the table.
221   *
222   * @param key the key to be assigned to
223   * @param value the value that will be assigned
224   * @return False if the table key already exists with a different type
225   */
226  public static boolean putNumber(String key, double value) {
227    return getEntry(key).setDouble(value);
228  }
229
230  /**
231   * Set the value in the table if key does not exist.
232   *
233   * @param key the key
234   * @param defaultValue the value to set if key does not exist
235   * @return True if the key did not already exist, otherwise False
236   */
237  public static boolean setDefaultNumber(String key, double defaultValue) {
238    return getEntry(key).setDefaultDouble(defaultValue);
239  }
240
241  /**
242   * Returns the number the key maps to. If the key does not exist or is of different type, it will
243   * return the default value.
244   *
245   * @param key the key to look up
246   * @param defaultValue the value to be returned if no value is found
247   * @return the value associated with the given key or the given default value if there is no value
248   *     associated with the key
249   */
250  public static double getNumber(String key, double defaultValue) {
251    return getEntry(key).getDouble(defaultValue);
252  }
253
254  /**
255   * Put a string in the table.
256   *
257   * @param key the key to be assigned to
258   * @param value the value that will be assigned
259   * @return False if the table key already exists with a different type
260   */
261  public static boolean putString(String key, String value) {
262    return getEntry(key).setString(value);
263  }
264
265  /**
266   * Set the value in the table if key does not exist.
267   *
268   * @param key the key
269   * @param defaultValue the value to set if key does not exist
270   * @return True if the key did not already exist, otherwise False
271   */
272  public static boolean setDefaultString(String key, String defaultValue) {
273    return getEntry(key).setDefaultString(defaultValue);
274  }
275
276  /**
277   * Returns the string the key maps to. If the key does not exist or is of different type, it will
278   * return the default value.
279   *
280   * @param key the key to look up
281   * @param defaultValue the value to be returned if no value is found
282   * @return the value associated with the given key or the given default value if there is no value
283   *     associated with the key
284   */
285  public static String getString(String key, String defaultValue) {
286    return getEntry(key).getString(defaultValue);
287  }
288
289  /**
290   * Put a boolean array in the table.
291   *
292   * @param key the key to be assigned to
293   * @param value the value that will be assigned
294   * @return False if the table key already exists with a different type
295   */
296  public static boolean putBooleanArray(String key, boolean[] value) {
297    return getEntry(key).setBooleanArray(value);
298  }
299
300  /**
301   * Put a boolean array in the table.
302   *
303   * @param key the key to be assigned to
304   * @param value the value that will be assigned
305   * @return False if the table key already exists with a different type
306   */
307  public static boolean putBooleanArray(String key, Boolean[] value) {
308    return getEntry(key).setBooleanArray(value);
309  }
310
311  /**
312   * Set the value in the table if key does not exist.
313   *
314   * @param key the key
315   * @param defaultValue the value to set if key does not exist
316   * @return True if the key did not already exist, otherwise False
317   */
318  public static boolean setDefaultBooleanArray(String key, boolean[] defaultValue) {
319    return getEntry(key).setDefaultBooleanArray(defaultValue);
320  }
321
322  /**
323   * Set the value in the table if key does not exist.
324   *
325   * @param key the key
326   * @param defaultValue the value to set if key does not exist
327   * @return True if the key did not already exist, otherwise False
328   */
329  public static boolean setDefaultBooleanArray(String key, Boolean[] defaultValue) {
330    return getEntry(key).setDefaultBooleanArray(defaultValue);
331  }
332
333  /**
334   * Returns the boolean array the key maps to. If the key does not exist or is of different type,
335   * it will return the default value.
336   *
337   * @param key the key to look up
338   * @param defaultValue the value to be returned if no value is found
339   * @return the value associated with the given key or the given default value if there is no value
340   *     associated with the key
341   */
342  public static boolean[] getBooleanArray(String key, boolean[] defaultValue) {
343    return getEntry(key).getBooleanArray(defaultValue);
344  }
345
346  /**
347   * Returns the boolean array the key maps to. If the key does not exist or is of different type,
348   * it will return the default value.
349   *
350   * @param key the key to look up
351   * @param defaultValue the value to be returned if no value is found
352   * @return the value associated with the given key or the given default value if there is no value
353   *     associated with the key
354   */
355  public static Boolean[] getBooleanArray(String key, Boolean[] defaultValue) {
356    return getEntry(key).getBooleanArray(defaultValue);
357  }
358
359  /**
360   * Put a number array in the table.
361   *
362   * @param key the key to be assigned to
363   * @param value the value that will be assigned
364   * @return False if the table key already exists with a different type
365   */
366  public static boolean putNumberArray(String key, double[] value) {
367    return getEntry(key).setDoubleArray(value);
368  }
369
370  /**
371   * Put a number array in the table.
372   *
373   * @param key the key to be assigned to
374   * @param value the value that will be assigned
375   * @return False if the table key already exists with a different type
376   */
377  public static boolean putNumberArray(String key, Double[] value) {
378    return getEntry(key).setNumberArray(value);
379  }
380
381  /**
382   * Set the value in the table if key does not exist.
383   *
384   * @param key the key
385   * @param defaultValue the value to set if key does not exist
386   * @return True if the key did not already exist, otherwise False
387   */
388  public static boolean setDefaultNumberArray(String key, double[] defaultValue) {
389    return getEntry(key).setDefaultDoubleArray(defaultValue);
390  }
391
392  /**
393   * Set the value in the table if key does not exist.
394   *
395   * @param key the key
396   * @param defaultValue the value to set if key does not exist
397   * @return True if the key did not already exist, otherwise False
398   */
399  public static boolean setDefaultNumberArray(String key, Double[] defaultValue) {
400    return getEntry(key).setDefaultNumberArray(defaultValue);
401  }
402
403  /**
404   * Returns the number array the key maps to. If the key does not exist or is of different type, it
405   * will return the default value.
406   *
407   * @param key the key to look up
408   * @param defaultValue the value to be returned if no value is found
409   * @return the value associated with the given key or the given default value if there is no value
410   *     associated with the key
411   */
412  public static double[] getNumberArray(String key, double[] defaultValue) {
413    return getEntry(key).getDoubleArray(defaultValue);
414  }
415
416  /**
417   * Returns the number array the key maps to. If the key does not exist or is of different type, it
418   * will return the default value.
419   *
420   * @param key the key to look up
421   * @param defaultValue the value to be returned if no value is found
422   * @return the value associated with the given key or the given default value if there is no value
423   *     associated with the key
424   */
425  public static Double[] getNumberArray(String key, Double[] defaultValue) {
426    return getEntry(key).getDoubleArray(defaultValue);
427  }
428
429  /**
430   * Put a string array in the table.
431   *
432   * @param key the key to be assigned to
433   * @param value the value that will be assigned
434   * @return False if the table key already exists with a different type
435   */
436  public static boolean putStringArray(String key, String[] value) {
437    return getEntry(key).setStringArray(value);
438  }
439
440  /**
441   * Set the value in the table if key does not exist.
442   *
443   * @param key the key
444   * @param defaultValue the value to set if key does not exist
445   * @return True if the key did not already exist, otherwise False
446   */
447  public static boolean setDefaultStringArray(String key, String[] defaultValue) {
448    return getEntry(key).setDefaultStringArray(defaultValue);
449  }
450
451  /**
452   * Returns the string array the key maps to. If the key does not exist or is of different type, it
453   * will return the default value.
454   *
455   * @param key the key to look up
456   * @param defaultValue the value to be returned if no value is found
457   * @return the value associated with the given key or the given default value if there is no value
458   *     associated with the key
459   */
460  public static String[] getStringArray(String key, String[] defaultValue) {
461    return getEntry(key).getStringArray(defaultValue);
462  }
463
464  /**
465   * Put a raw value (byte array) in the table.
466   *
467   * @param key the key to be assigned to
468   * @param value the value that will be assigned
469   * @return False if the table key already exists with a different type
470   */
471  public static boolean putRaw(String key, byte[] value) {
472    return getEntry(key).setRaw(value);
473  }
474
475  /**
476   * Set the value in the table if key does not exist.
477   *
478   * @param key the key
479   * @param defaultValue the value to set if key does not exist
480   * @return True if the key did not already exist, otherwise False
481   */
482  public static boolean setDefaultRaw(String key, byte[] defaultValue) {
483    return getEntry(key).setDefaultRaw(defaultValue);
484  }
485
486  /**
487   * Returns the raw value (byte array) the key maps to. If the key does not exist or is of
488   * different type, it will return the default value.
489   *
490   * @param key the key to look up
491   * @param defaultValue the value to be returned if no value is found
492   * @return the value associated with the given key or the given default value if there is no value
493   *     associated with the key
494   */
495  public static byte[] getRaw(String key, byte[] defaultValue) {
496    return getEntry(key).getRaw(defaultValue);
497  }
498
499  /**
500   * Posts a task from a listener to the ListenerExecutor, so that it can be run synchronously from
501   * the main loop on the next call to {@link SmartDashboard#updateValues()}.
502   *
503   * @param task The task to run synchronously from the main thread.
504   */
505  public static void postListenerTask(Runnable task) {
506    listenerExecutor.execute(task);
507  }
508
509  /** Puts all sendable data to the dashboard. */
510  public static synchronized void updateValues() {
511    // Execute posted listener tasks
512    listenerExecutor.runListenerTasks();
513    for (Sendable data : tablesToData.values()) {
514      SendableRegistry.update(data);
515    }
516  }
517}