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