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}